mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-02-21 10:30:16 +00:00
Merge branch 'testing' into conflict_resolution
This commit is contained in:
commit
3aefb3f6e8
113 changed files with 6356 additions and 5695 deletions
analysis_options.yaml
android/app
crypto_plugins
docs
ios
lib
db
electrumx_rpc
cached_electrumx_client.dartclient_manager.dartelectrumx_chain_height_service.dartelectrumx_client.dartrpc.dart
main.dartmodels
pages
add_wallet_views
paynym/subwidgets
receive_view
settings_views/global_settings_view
wallets_view/sub_widgets
pages_desktop_specific
services
supported_coins.dartthemes
utilities
address_utils.dart
amount
bip32_utils.dartblock_explorers.dartconnection_check
constants.dartdefault_nodes.dartenums
prefs.darttext_styles.dartwallets
crypto_currency
coins
banano.dartbitcoin.dartbitcoin_frost.dartbitcoincash.dartdogecoin.dartecash.dartepiccash.dartethereum.dartfiro.dartlitecoin.dartmonero.dartnamecoin.dartnano.dartparticl.dartsolana.dartstellar.darttezos.dartwownero.dart
crypto_currency.dartintermediate
isar/models
wallet
widgets
macos
pubspec.lockpubspec.yamlscripts
test
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
96
lib/electrumx_rpc/client_manager.dart
Normal file
96
lib/electrumx_rpc/client_manager.dart
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
}
|
|
@ -687,6 +687,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
appBarTheme: AppBarTheme(
|
||||
centerTitle: false,
|
||||
color: colorScheme.background,
|
||||
surfaceTintColor: colorScheme.background,
|
||||
elevation: 0,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
|
|
|
@ -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.?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>()!
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
// },
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
78
lib/supported_coins.dart
Normal 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),
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
61
lib/wallets/crypto_currency/coins/solana.dart
Normal file
61
lib/wallets/crypto_currency/coins/solana.dart
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
478
lib/wallets/wallet/impl/solana_wallet.dart
Normal file
478
lib/wallets/wallet/impl/solana_wallet.dart
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -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,
|
||||
|
|
44
lib/widgets/dialogs/tor_warning_dialog.dart
Normal file
44
lib/widgets/dialogs/tor_warning_dialog.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -195,4 +195,4 @@ SPEC CHECKSUMS:
|
|||
|
||||
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
COCOAPODS: 1.14.3
|
||||
|
|
|
@ -277,7 +277,7 @@
|
|||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1430;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C80D4294CF70F00263BE5 = {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1430"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
190
pubspec.lock
190
pubspec.lock
|
@ -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"
|
||||
|
|
49
pubspec.yaml
49
pubspec.yaml
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -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
|
@ -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
Loading…
Reference in a new issue