mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-22 19:39:22 +00:00
Merge remote-tracking branch 'origin' into add-bitcoincash
This commit is contained in:
commit
25c395e070
61 changed files with 5022 additions and 1562 deletions
54
README.md
54
README.md
|
@ -1,18 +1,29 @@
|
|||
[![codecov](https://codecov.io/gh/cypherstack/stack_wallet/branch/main/graph/badge.svg?token=PM1N56UTEW)](https://codecov.io/gh/cypherstack/stack_wallet)
|
||||
|
||||
# Stack Wallet
|
||||
put details here
|
||||
Stack Wallet is a fully open source cryptocurrency wallet. With an easy to use user interface and quick and speedy transactions, this wallet is ideal for anyone no matter how much they know about the cryptocurrency space. The app is actively maintained to provide new user friendly features.
|
||||
|
||||
[![Playstore](https://bluewallet.io/img/play-store-badge.svg)](https://play.google.com/store/apps/details?id=com.cypherstack.stackwallet)
|
||||
|
||||
## Feature List
|
||||
put features here
|
||||
|
||||
Highlights include:
|
||||
- 5 Different cryptocurrencies
|
||||
- All private keys and seeds stay on device and are never shared.
|
||||
- Easy backup and restore feature to save all the information that's important to you.
|
||||
- Trading cryptocurrencies through our partners.
|
||||
- Custom address book
|
||||
- Favorite wallets with fast syncing
|
||||
- Custom Nodes.
|
||||
- Open source software.
|
||||
|
||||
## Build and run
|
||||
### Prerequisites
|
||||
- Flutter 3.0.5
|
||||
- Flutter SDK Requirement (>=2.12.0, up until <3.0.0)
|
||||
- Android/iOS dev setup (Android Studio, xCode and subsequent dependencies)
|
||||
- The only OS supported for building is Ubuntu 20.04
|
||||
- A machine with at least 100 GB of Storage
|
||||
- Flutter 3.0.5 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install)
|
||||
- Dart SDK Requirement (>=2.17.0, up until <3.0.0)
|
||||
- Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies)
|
||||
|
||||
After that download the project and init the submodules
|
||||
```
|
||||
|
@ -21,31 +32,34 @@ cd stack_wallet
|
|||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
You will need to install all dependencies listed in each of the plugins in the crypto_plugins folder. (eg. [Monero](https://github.com/cypherstack/flutter_libmonero), [Epic Cash](https://github.com/cypherstack/flutter_libepiccash) ) as of Sep 8th 2022 that is:
|
||||
|
||||
Install [Rust](https://www.rust-lang.org/tools/install)
|
||||
```
|
||||
cargo install cargo-ndk
|
||||
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
|
||||
|
||||
sudo apt install libc6-dev-i386
|
||||
sudo apt install build-essential cmake git libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev pkg-config llvm
|
||||
sudo apt install build-essential debhelper cmake libclang-dev libncurses5-dev clang libncursesw5-dev cargo rustc opencl-headers libssl-dev pkg-config ocl-icd-opencl-dev
|
||||
sudo apt install unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless
|
||||
```
|
||||
|
||||
Building plugins for Android
|
||||
```
|
||||
cd crypto_plugins/flutter_liblelantus/scripts/android/
|
||||
// note if you are on a mac go one directory further to android_on_mac
|
||||
cd scripts/android/
|
||||
./build_all.sh
|
||||
// when finished go back to the root directory
|
||||
cd ../../../..
|
||||
```
|
||||
|
||||
Building plugins for IOS
|
||||
|
||||
```
|
||||
cd crypto_plugins/flutter_liblelantus/scripts/ios/
|
||||
./build_all.sh
|
||||
// when finished go back to the root directory
|
||||
cd ../../../..
|
||||
cd ../..
|
||||
```
|
||||
|
||||
Building plugins for testing on Linux
|
||||
|
||||
```
|
||||
cd crypto_plugins/flutter_liblelantus/scripts/linux/
|
||||
cd scripts/linux/
|
||||
./build_all.sh
|
||||
// when finished go back to the root directory
|
||||
cd ../../../..
|
||||
cd ../..
|
||||
```
|
||||
|
||||
Finally, plug in your android device or use the emulator available via Android Studio and then run the following commands:
|
||||
|
@ -53,3 +67,5 @@ Finally, plug in your android device or use the emulator available via Android S
|
|||
flutter pub get
|
||||
flutter run
|
||||
```
|
||||
|
||||
Note on Emulators: Only x86_64 emulators are supported, x86 emulators will not work
|
||||
|
|
14
assets/svg/share-2.svg
Normal file
14
assets/svg/share-2.svg
Normal file
|
@ -0,0 +1,14 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_5024_11503)">
|
||||
<path d="M9 4C9.82843 4 10.5 3.32843 10.5 2.5C10.5 1.67157 9.82843 1 9 1C8.17157 1 7.5 1.67157 7.5 2.5C7.5 3.32843 8.17157 4 9 4Z" fill="white" stroke="#111111" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 7.5C3.82843 7.5 4.5 6.82843 4.5 6C4.5 5.17157 3.82843 4.5 3 4.5C2.17157 4.5 1.5 5.17157 1.5 6C1.5 6.82843 2.17157 7.5 3 7.5Z" stroke="#111111" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 11C9.82843 11 10.5 10.3284 10.5 9.5C10.5 8.67157 9.82843 8 9 8C8.17157 8 7.5 8.67157 7.5 9.5C7.5 10.3284 8.17157 11 9 11Z" stroke="#111111" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.29504 6.75488L7.71004 8.74488" stroke="#111111" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.70504 3.25488L4.29504 5.24488" stroke="#111111" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5024_11503">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
13
assets/svg/tx-icon-anonymize-failed.svg
Normal file
13
assets/svg/tx-icon-anonymize-failed.svg
Normal file
|
@ -0,0 +1,13 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M23.0154 16.7681C23.6489 15.3066 24 13.6943 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24C13.6943 24 15.3066 23.6489 16.7681 23.0154C16.2832 22.2973 16 21.4317 16 20.5C16 18.0147 18.0147 16 20.5 16C21.4317 16 22.2973 16.2832 23.0154 16.7681Z" fill="#E0E3E3"/>
|
||||
<g clip-path="url(#clip0_5035_57298)">
|
||||
<path d="M10.0169 11.6619C10.1425 12.2062 9.54581 12.6328 9.0721 12.3345L8.43612 11.9367L7.10397 14.0678C6.7551 14.6258 7.15642 15.3497 7.81454 15.3497H8.65256C9.11423 15.3497 9.48954 15.7245 9.48954 16.1862C9.48954 16.6479 9.11423 17.025 8.65256 17.025H7.81716C5.84484 17.025 4.64251 14.8569 5.68493 13.183L7.01525 11.0521L6.3777 10.6516C5.90373 10.3533 6.02464 9.63093 6.56954 9.5053L8.96244 8.95177C9.18725 8.90335 9.41495 9.03944 9.46729 9.26714L10.0169 11.6619ZM12.71 7.36601L13.7896 9.09702L13.1549 9.49169C12.6796 9.7877 12.7998 10.5108 13.3454 10.6372L15.7375 11.1908C15.9632 11.243 16.1884 11.1021 16.2403 10.8764L16.7909 8.48355C16.9163 7.93892 16.3211 7.51389 15.8469 7.80937L15.2093 8.20718L14.1311 6.48088C13.1476 4.90691 10.8544 4.90586 9.87034 6.47927L9.67667 6.78866C9.43327 7.17732 9.55366 7.6963 9.941 7.94023C10.3315 8.18517 10.851 8.0681 11.0954 7.67746L11.2902 7.36628C11.6239 6.83263 12.3907 6.85226 12.71 7.36601ZM18.316 13.1851L17.8713 12.4716C17.627 12.0796 17.1108 11.9597 16.7187 12.2044C16.3277 12.4483 16.2079 12.9673 16.4523 13.3581L16.897 14.0686C17.2464 14.6266 16.845 15.3511 16.1867 15.3511H13.6744L13.675 14.5989C13.675 14.0393 12.9985 13.759 12.6028 14.1547L10.8647 15.8933C10.7019 16.0562 10.7019 16.3228 10.8648 16.4856L12.6031 18.2218C12.9989 18.617 13.6749 18.3366 13.6749 17.7774L13.6743 17.0268H16.1831C18.1564 17.0271 19.3603 14.8575 18.316 13.1851Z" fill="#494949"/>
|
||||
</g>
|
||||
<circle cx="20.5" cy="20.5" r="3.5" fill="#C00205"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.1465 21.2072C18.9512 21.4025 18.9512 21.7191 19.1465 21.9143C19.3418 22.1096 19.6583 22.1096 19.8536 21.9143L20.5293 21.2386L21.2054 21.9146C21.4007 22.1099 21.7172 22.1099 21.9125 21.9146C22.1078 21.7194 22.1078 21.4028 21.9125 21.2075L21.2365 20.5315L21.9749 19.793C22.1702 19.5977 22.1702 19.2812 21.9749 19.0859C21.7797 18.8906 21.4631 18.8906 21.2678 19.0859L20.5293 19.8244L19.7912 19.0862C19.5959 18.8909 19.2793 18.8909 19.0841 19.0862C18.8888 19.2815 18.8888 19.598 19.0841 19.7933L19.8222 20.5315L19.1465 21.2072Z" fill="white"/>
|
||||
<defs>
|
||||
<clipPath id="clip0_5035_57298">
|
||||
<rect width="13.4" height="13.4" fill="white" transform="translate(5.30078 5.2998)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
13
assets/svg/tx-icon-anonymize-pending.svg
Normal file
13
assets/svg/tx-icon-anonymize-pending.svg
Normal file
|
@ -0,0 +1,13 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M23.0154 16.7681C23.6489 15.3066 24 13.6943 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24C13.6943 24 15.3066 23.6489 16.7681 23.0154C16.2832 22.2973 16 21.4317 16 20.5C16 18.0147 18.0147 16 20.5 16C21.4317 16 22.2973 16.2832 23.0154 16.7681Z" fill="#E0E3E3"/>
|
||||
<circle cx="20.5" cy="20.5" r="3.5" fill="#F4C517"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.5 18.5C20.7761 18.5 21 18.7239 21 19V20H21.5C21.7761 20 22 20.2239 22 20.5C22 20.7761 21.7761 21 21.5 21H20.5C20.2239 21 20 20.7761 20 20.5V19C20 18.7239 20.2239 18.5 20.5 18.5Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_5035_57297)">
|
||||
<path d="M10.0169 11.6619C10.1425 12.2062 9.54581 12.6328 9.0721 12.3345L8.43612 11.9367L7.10397 14.0678C6.7551 14.6258 7.15642 15.3497 7.81454 15.3497H8.65256C9.11423 15.3497 9.48954 15.7245 9.48954 16.1862C9.48954 16.6479 9.11423 17.025 8.65256 17.025H7.81716C5.84484 17.025 4.64251 14.8569 5.68493 13.183L7.01525 11.0521L6.3777 10.6516C5.90373 10.3533 6.02464 9.63093 6.56954 9.5053L8.96244 8.95177C9.18725 8.90335 9.41495 9.03944 9.46729 9.26714L10.0169 11.6619ZM12.71 7.36601L13.7896 9.09702L13.1549 9.49169C12.6796 9.7877 12.7998 10.5108 13.3454 10.6372L15.7375 11.1908C15.9632 11.243 16.1884 11.1021 16.2403 10.8764L16.7909 8.48355C16.9163 7.93892 16.3211 7.51389 15.8469 7.80937L15.2093 8.20718L14.1311 6.48088C13.1476 4.90691 10.8544 4.90586 9.87034 6.47927L9.67667 6.78866C9.43327 7.17732 9.55366 7.6963 9.941 7.94023C10.3315 8.18517 10.851 8.0681 11.0954 7.67746L11.2902 7.36628C11.6239 6.83263 12.3907 6.85226 12.71 7.36601ZM18.316 13.1851L17.8713 12.4716C17.627 12.0796 17.1108 11.9597 16.7187 12.2044C16.3277 12.4483 16.2079 12.9673 16.4523 13.3581L16.897 14.0686C17.2464 14.6266 16.845 15.3511 16.1867 15.3511H13.6744L13.675 14.5989C13.675 14.0393 12.9985 13.759 12.6028 14.1547L10.8647 15.8933C10.7019 16.0562 10.7019 16.3228 10.8648 16.4856L12.6031 18.2218C12.9989 18.617 13.6749 18.3366 13.6749 17.7774L13.6743 17.0268H16.1831C18.1564 17.0271 19.3603 14.8575 18.316 13.1851Z" fill="#494949"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5035_57297">
|
||||
<rect width="13.4" height="13.4" fill="white" transform="translate(5.30078 5.2998)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
16
assets/svg/tx-icon-anonymize.svg
Normal file
16
assets/svg/tx-icon-anonymize.svg
Normal file
|
@ -0,0 +1,16 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_5035_57299)">
|
||||
<circle cx="12" cy="12" r="12" fill="#E0E3E3"/>
|
||||
<g clip-path="url(#clip1_5035_57299)">
|
||||
<path d="M9.92728 11.6469C10.0585 12.2157 9.4351 12.6614 8.94018 12.3497L8.27572 11.934L6.88393 14.1606C6.51943 14.7436 6.93872 15.4999 7.62631 15.4999H8.50185C8.9842 15.4999 9.37631 15.8915 9.37631 16.3738C9.37631 16.8562 8.9842 17.2502 8.50185 17.2502H7.62904C5.56842 17.2502 4.31225 14.9851 5.40135 13.2361L6.79123 11.0098L6.12514 10.5915C5.62994 10.2797 5.75627 9.52505 6.32557 9.3938L8.8256 8.81548C9.06049 8.7649 9.29838 8.90709 9.35307 9.14498L9.92728 11.6469ZM12.741 7.15873L13.8689 8.96724L13.2058 9.37959C12.7092 9.68884 12.8347 10.4444 13.4048 10.5764L15.904 11.1547C16.1398 11.2093 16.3752 11.0621 16.4293 10.8263L17.0046 8.32631C17.1356 7.75728 16.5138 7.31322 16.0183 7.62193L15.3522 8.03755L14.2257 6.23396C13.1981 4.58951 10.8023 4.58841 9.77416 6.23227L9.57182 6.55552C9.31752 6.96158 9.4433 7.50381 9.84799 7.75865C10.256 8.01456 10.7987 7.89225 11.0541 7.48412L11.2576 7.159C11.6062 6.60146 12.4074 6.62197 12.741 7.15873ZM18.598 13.2383L18.1334 12.4929C17.8781 12.0833 17.3388 11.9581 16.9292 12.2138C16.5207 12.4686 16.3954 13.0108 16.6508 13.4191L17.1154 14.1615C17.4804 14.7444 17.0612 15.5013 16.3733 15.5013H13.7486L13.7492 14.7154C13.7492 14.1308 13.0424 13.838 12.6289 14.2514L10.813 16.0679C10.6429 16.238 10.6429 16.5166 10.8132 16.6867L12.6293 18.5006C13.0428 18.9135 13.7491 18.6206 13.7491 18.0363L13.7484 17.2521H16.3696C18.4312 17.2524 19.689 14.9856 18.598 13.2383Z" fill="#494949"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5035_57299">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_5035_57299">
|
||||
<rect width="14" height="14" fill="white" transform="translate(5 5)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -1,5 +1,6 @@
|
|||
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||
import 'package:stackwallet/hive/db.dart';
|
||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
|
@ -59,6 +60,20 @@ class CachedElectrumX {
|
|||
"setHash": "",
|
||||
"coins": <dynamic>[],
|
||||
};
|
||||
|
||||
// try up to 3 times
|
||||
for (int i = 0; i < 3; i++) {
|
||||
final result = await getInitialAnonymitySetCache(groupId);
|
||||
if (result != null) {
|
||||
set["setHash"] = result["setHash"];
|
||||
set["blockHash"] = result["blockHash"];
|
||||
set["coins"] = result["coins"];
|
||||
Logging.instance.log(
|
||||
"Populated initial anon set cache for group $groupId",
|
||||
level: LogLevel.Info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
set = Map<String, dynamic>.from(cachedSet);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import 'package:stackwallet/models/trade_wallet_lookup.dart';
|
|||
import 'package:stackwallet/services/wallets_service.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
||||
class DB {
|
||||
static const String boxNameAddressBook = "addressBook";
|
||||
static const String boxNameDebugInfo = "debugInfoBox";
|
||||
|
@ -27,6 +29,7 @@ class DB {
|
|||
static const String boxNamePrefs = "prefs";
|
||||
static const String boxNameWalletsToDeleteOnStart = "walletsToDeleteOnStart";
|
||||
static const String boxNamePriceCache = "priceAPIPrice24hCache";
|
||||
static const String boxNameDBInfo = "dbInfo";
|
||||
|
||||
String boxNameTxCache({required Coin coin}) => "${coin.name}_txCache";
|
||||
String boxNameSetCache({required Coin coin}) =>
|
||||
|
@ -50,6 +53,7 @@ class DB {
|
|||
late final Box<xmr.WalletInfo> _walletInfoSource;
|
||||
late final Box<dynamic> _boxPrefs;
|
||||
late final Box<TradeWalletLookup> _boxTradeLookup;
|
||||
late final Box<dynamic> _boxDBInfo;
|
||||
|
||||
final Map<String, Box<dynamic>> _walletBoxes = {};
|
||||
|
||||
|
@ -80,13 +84,40 @@ class DB {
|
|||
// open hive boxes
|
||||
Future<void> init() async {
|
||||
if (!_initialized) {
|
||||
if (Hive.isBoxOpen(boxNameDBInfo)) {
|
||||
_boxDBInfo = Hive.box<dynamic>(boxNameDBInfo);
|
||||
} else {
|
||||
_boxDBInfo = await Hive.openBox<dynamic>(boxNameDBInfo);
|
||||
}
|
||||
await Hive.openBox<String>(boxNameWalletsToDeleteOnStart);
|
||||
_boxPrefs = await Hive.openBox<dynamic>(boxNamePrefs);
|
||||
|
||||
if (Hive.isBoxOpen(boxNamePrefs)) {
|
||||
_boxPrefs = Hive.box<dynamic>(boxNamePrefs);
|
||||
} else {
|
||||
_boxPrefs = await Hive.openBox<dynamic>(boxNamePrefs);
|
||||
}
|
||||
|
||||
_boxAddressBook = await Hive.openBox<dynamic>(boxNameAddressBook);
|
||||
_boxDebugInfo = await Hive.openBox<String>(boxNameDebugInfo);
|
||||
_boxNodeModels = await Hive.openBox<NodeModel>(boxNameNodeModels);
|
||||
_boxPrimaryNodes = await Hive.openBox<NodeModel>(boxNamePrimaryNodes);
|
||||
_boxAllWalletsData = await Hive.openBox<dynamic>(boxNameAllWalletsData);
|
||||
|
||||
if (Hive.isBoxOpen(boxNameNodeModels)) {
|
||||
_boxNodeModels = Hive.box<NodeModel>(boxNameNodeModels);
|
||||
} else {
|
||||
_boxNodeModels = await Hive.openBox<NodeModel>(boxNameNodeModels);
|
||||
}
|
||||
|
||||
if (Hive.isBoxOpen(boxNamePrimaryNodes)) {
|
||||
_boxPrimaryNodes = Hive.box<NodeModel>(boxNamePrimaryNodes);
|
||||
} else {
|
||||
_boxPrimaryNodes = await Hive.openBox<NodeModel>(boxNamePrimaryNodes);
|
||||
}
|
||||
|
||||
if (Hive.isBoxOpen(boxNameAllWalletsData)) {
|
||||
_boxAllWalletsData = Hive.box<dynamic>(boxNameAllWalletsData);
|
||||
} else {
|
||||
_boxAllWalletsData = await Hive.openBox<dynamic>(boxNameAllWalletsData);
|
||||
}
|
||||
|
||||
_boxNotifications =
|
||||
await Hive.openBox<NotificationModel>(boxNameNotifications);
|
||||
_boxWatchedTransactions =
|
||||
|
@ -112,12 +143,29 @@ class DB {
|
|||
|
||||
Future<void> _loadWalletBoxes() async {
|
||||
final names = _boxAllWalletsData.get("names") as Map? ?? {};
|
||||
names.removeWhere((name, dyn) {
|
||||
final jsonObject = Map<String, dynamic>.from(dyn as Map);
|
||||
try {
|
||||
Coin.values.byName(jsonObject["coin"] as String);
|
||||
return false;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Error, ${jsonObject["coin"]} does not exist, $name wallet cannot be loaded",
|
||||
level: LogLevel.Error);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
final mapped = Map<String, dynamic>.from(names).map((name, dyn) => MapEntry(
|
||||
name, WalletInfo.fromJson(Map<String, dynamic>.from(dyn as Map))));
|
||||
|
||||
for (final entry in mapped.entries) {
|
||||
_walletBoxes[entry.value.walletId] =
|
||||
await Hive.openBox<dynamic>(entry.value.walletId);
|
||||
if (Hive.isBoxOpen(entry.value.walletId)) {
|
||||
_walletBoxes[entry.value.walletId] =
|
||||
Hive.box<dynamic>(entry.value.walletId);
|
||||
} else {
|
||||
_walletBoxes[entry.value.walletId] =
|
||||
await Hive.openBox<dynamic>(entry.value.walletId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ import 'package:stackwallet/services/trade_service.dart';
|
|||
import 'package:stackwallet/services/wallets.dart';
|
||||
import 'package:stackwallet/utilities/cfcolors.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/db_version_migration.dart';
|
||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
|
@ -119,18 +120,16 @@ void main() async {
|
|||
|
||||
Hive.registerAdapter(UnspentCoinsInfoAdapter());
|
||||
|
||||
await Hive.openBox<dynamic>(DB.boxNameDBInfo);
|
||||
int dbVersion = DB.instance.get<dynamic>(
|
||||
boxName: DB.boxNameDBInfo, key: "hive_data_version") as int? ??
|
||||
0;
|
||||
if (dbVersion < Constants.currentHiveDbVersion) {
|
||||
await DbVersionMigrator().migrate(dbVersion);
|
||||
}
|
||||
|
||||
monero.onStartup();
|
||||
|
||||
// final wallets = await Hive.openBox('wallets');
|
||||
// await wallets.put('currentWalletName', "");
|
||||
|
||||
// NOT USED YET
|
||||
// int dbVersion = await wallets.get("db_version");
|
||||
// if (dbVersion == null || dbVersion < Constants.currentDbVersion) {
|
||||
// if (dbVersion == null) dbVersion = 0;
|
||||
// await DbVersionMigrator().migrate(dbVersion);
|
||||
// }
|
||||
|
||||
// SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||
// overlays: [SystemUiOverlay.bottom]);
|
||||
await NotificationApi.init();
|
||||
|
@ -344,20 +343,23 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
_prefs = ref.read(prefsChangeNotifierProvider);
|
||||
_wallets = ref.read(walletsChangeNotifierProvider);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
// fetch open file if it exists
|
||||
await getOpenFile();
|
||||
if (Platform.isAndroid) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
// fetch open file if it exists
|
||||
await getOpenFile();
|
||||
|
||||
if (ref.read(openedFromSWBFileStringStateProvider.state).state != null) {
|
||||
// waiting for loading to complete before going straight to restore if the app was opened via file
|
||||
await loadingCompleter.future;
|
||||
if (ref.read(openedFromSWBFileStringStateProvider.state).state !=
|
||||
null) {
|
||||
// waiting for loading to complete before going straight to restore if the app was opened via file
|
||||
await loadingCompleter.future;
|
||||
|
||||
await goToRestoreSWB(
|
||||
ref.read(openedFromSWBFileStringStateProvider.state).state!);
|
||||
ref.read(openedFromSWBFileStringStateProvider.state).state = null;
|
||||
}
|
||||
// ref.read(shouldShowLockscreenOnResumeStateProvider.state).state = false;
|
||||
});
|
||||
await goToRestoreSWB(
|
||||
ref.read(openedFromSWBFileStringStateProvider.state).state!);
|
||||
ref.read(openedFromSWBFileStringStateProvider.state).state = null;
|
||||
}
|
||||
// ref.read(shouldShowLockscreenOnResumeStateProvider.state).state = false;
|
||||
});
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
@ -378,14 +380,16 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
case AppLifecycleState.paused:
|
||||
break;
|
||||
case AppLifecycleState.resumed:
|
||||
// fetch open file if it exists
|
||||
await getOpenFile();
|
||||
// go straight to restore if the app was resumed via file
|
||||
if (ref.read(openedFromSWBFileStringStateProvider.state).state !=
|
||||
null) {
|
||||
await goToRestoreSWB(
|
||||
ref.read(openedFromSWBFileStringStateProvider.state).state!);
|
||||
ref.read(openedFromSWBFileStringStateProvider.state).state = null;
|
||||
if (Platform.isAndroid) {
|
||||
// fetch open file if it exists
|
||||
await getOpenFile();
|
||||
// go straight to restore if the app was resumed via file
|
||||
if (ref.read(openedFromSWBFileStringStateProvider.state).state !=
|
||||
null) {
|
||||
await goToRestoreSWB(
|
||||
ref.read(openedFromSWBFileStringStateProvider.state).state!);
|
||||
ref.read(openedFromSWBFileStringStateProvider.state).state = null;
|
||||
}
|
||||
}
|
||||
// if (ref.read(hasAuthenticatedOnStartStateProvider.state).state &&
|
||||
// ref.read(shouldShowLockscreenOnResumeStateProvider.state).state) {
|
||||
|
@ -419,6 +423,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
}
|
||||
}
|
||||
|
||||
/// should only be called on android currently
|
||||
Future<void> getOpenFile() async {
|
||||
// update provider with new file content state
|
||||
ref.read(openedFromSWBFileStringStateProvider.state).state =
|
||||
|
@ -432,6 +437,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
level: LogLevel.Info);
|
||||
}
|
||||
|
||||
/// should only be called on android currently
|
||||
Future<void> resetOpenPath() async {
|
||||
await platform.invokeMethod("resetOpenPath");
|
||||
}
|
||||
|
|
163
lib/models/exchange/change_now/cn_exchange_estimate.dart
Normal file
163
lib/models/exchange/change_now/cn_exchange_estimate.dart
Normal file
|
@ -0,0 +1,163 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
||||
enum CNEstimateType { direct, reverse }
|
||||
|
||||
enum CNFlowType implements Comparable<CNFlowType> {
|
||||
standard("standard"),
|
||||
fixedRate("fixed-rate");
|
||||
|
||||
const CNFlowType(this.value);
|
||||
|
||||
final String value;
|
||||
|
||||
@override
|
||||
int compareTo(CNFlowType other) => value.compareTo(other.value);
|
||||
}
|
||||
|
||||
class CNExchangeEstimate {
|
||||
/// Ticker of the currency you want to exchange
|
||||
final String fromCurrency;
|
||||
|
||||
/// Network of the currency you want to exchange
|
||||
final String fromNetwork;
|
||||
|
||||
/// Ticker of the currency you want to receive
|
||||
final String toCurrency;
|
||||
|
||||
/// Network of the currency you want to receive
|
||||
final String toNetwork;
|
||||
|
||||
/// Type of exchange flow. Enum: ["standard", "fixed-rate"]
|
||||
final CNFlowType flow;
|
||||
|
||||
/// Direction of exchange flow. Use "direct" value to set amount for
|
||||
/// currencyFrom and get amount of currencyTo. Use "reverse" value to set
|
||||
/// amount for currencyTo and get amount of currencyFrom.
|
||||
/// Enum: ["direct", "reverse"]
|
||||
final CNEstimateType type;
|
||||
|
||||
/// (Optional) Use rateId for fixed-rate flow. If this field is true, you
|
||||
/// could use returned field "rateId" in next method for creating transaction
|
||||
/// to freeze estimated amount that you got in this method. Current estimated
|
||||
/// amount would be valid until time in field "validUntil"
|
||||
final String? rateId;
|
||||
|
||||
/// Date and time before estimated amount would be freezed in case of using
|
||||
/// rateId. If you set param "useRateId" to true, you could use returned field
|
||||
/// "rateId" in next method for creating transaction to freeze estimated
|
||||
/// amount that you got in this method. Estimated amount would be valid until
|
||||
/// this date and time
|
||||
final String? validUntil;
|
||||
|
||||
/// Dash-separated min and max estimated time in minutes
|
||||
final String? transactionSpeedForecast;
|
||||
|
||||
/// Some warnings like warnings that transactions on this network
|
||||
/// take longer or that the currency has moved to another network
|
||||
final String? warningMessage;
|
||||
|
||||
/// Exchange amount of fromCurrency (in case when type=reverse it is an
|
||||
/// estimated value)
|
||||
final Decimal fromAmount;
|
||||
|
||||
/// Exchange amount of toCurrency (in case when type=direct it is an
|
||||
/// estimated value)
|
||||
final Decimal toAmount;
|
||||
|
||||
CNExchangeEstimate({
|
||||
required this.fromCurrency,
|
||||
required this.fromNetwork,
|
||||
required this.toCurrency,
|
||||
required this.toNetwork,
|
||||
required this.flow,
|
||||
required this.type,
|
||||
this.rateId,
|
||||
this.validUntil,
|
||||
this.transactionSpeedForecast,
|
||||
this.warningMessage,
|
||||
required this.fromAmount,
|
||||
required this.toAmount,
|
||||
});
|
||||
|
||||
factory CNExchangeEstimate.fromJson(Map<String, dynamic> json) {
|
||||
try {
|
||||
final flow = CNFlowType.values
|
||||
.firstWhere((element) => element.value == json["flow"]);
|
||||
final type = CNEstimateType.values
|
||||
.firstWhere((element) => element.name == json["type"]);
|
||||
|
||||
return CNExchangeEstimate(
|
||||
fromCurrency: json["fromCurrency"] as String,
|
||||
fromNetwork: json["fromNetwork"] as String,
|
||||
toCurrency: json["toCurrency"] as String,
|
||||
toNetwork: json["toNetwork"] as String,
|
||||
flow: flow,
|
||||
type: type,
|
||||
rateId: json["rateId"] as String?,
|
||||
validUntil: json["validUntil"] as String?,
|
||||
transactionSpeedForecast: json["transactionSpeedForecast"] as String?,
|
||||
warningMessage: json["warningMessage"] as String?,
|
||||
fromAmount: Decimal.parse(json["fromAmount"].toString()),
|
||||
toAmount: Decimal.parse(json["toAmount"].toString()),
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("Failed to parse: $json \n$e\n$s", level: LogLevel.Fatal);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
"fromCurrency": fromCurrency,
|
||||
"fromNetwork": fromNetwork,
|
||||
"toCurrency": toCurrency,
|
||||
"toNetwork": toNetwork,
|
||||
"flow": flow,
|
||||
"type": type,
|
||||
"rateId": rateId,
|
||||
"validUntil": validUntil,
|
||||
"transactionSpeedForecast": transactionSpeedForecast,
|
||||
"warningMessage": warningMessage,
|
||||
"fromAmount": fromAmount,
|
||||
"toAmount": toAmount,
|
||||
};
|
||||
}
|
||||
|
||||
CNExchangeEstimate copyWith({
|
||||
String? fromCurrency,
|
||||
String? fromNetwork,
|
||||
String? toCurrency,
|
||||
String? toNetwork,
|
||||
CNFlowType? flow,
|
||||
CNEstimateType? type,
|
||||
String? rateId,
|
||||
String? validUntil,
|
||||
String? transactionSpeedForecast,
|
||||
String? warningMessage,
|
||||
Decimal? fromAmount,
|
||||
Decimal? toAmount,
|
||||
}) {
|
||||
return CNExchangeEstimate(
|
||||
fromCurrency: fromCurrency ?? this.fromCurrency,
|
||||
fromNetwork: fromNetwork ?? this.fromNetwork,
|
||||
toCurrency: toCurrency ?? this.toCurrency,
|
||||
toNetwork: toNetwork ?? this.toNetwork,
|
||||
flow: flow ?? this.flow,
|
||||
type: type ?? this.type,
|
||||
rateId: rateId ?? this.rateId,
|
||||
validUntil: validUntil ?? this.validUntil,
|
||||
transactionSpeedForecast:
|
||||
transactionSpeedForecast ?? this.transactionSpeedForecast,
|
||||
warningMessage: warningMessage ?? this.warningMessage,
|
||||
fromAmount: fromAmount ?? this.fromAmount,
|
||||
toAmount: toAmount ?? this.toAmount,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "EstimatedExchangeAmount: ${toJson()}";
|
||||
}
|
||||
}
|
|
@ -1,10 +1,14 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/currency.dart';
|
||||
import 'package:stackwallet/services/change_now/change_now.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
||||
class EstimatedRateExchangeFormState extends ChangeNotifier {
|
||||
/// used in testing to inject mock
|
||||
ChangeNow? cnTesting;
|
||||
|
||||
Decimal? _fromAmount;
|
||||
Decimal? _toAmount;
|
||||
|
||||
|
@ -16,9 +20,43 @@ class EstimatedRateExchangeFormState extends ChangeNotifier {
|
|||
Currency? _from;
|
||||
Currency? _to;
|
||||
|
||||
void Function(String)? _onError;
|
||||
|
||||
Currency? get from => _from;
|
||||
Currency? get to => _to;
|
||||
|
||||
String get fromAmountString =>
|
||||
_fromAmount == null ? "" : _fromAmount!.toStringAsFixed(8);
|
||||
String get toAmountString =>
|
||||
_toAmount == null ? "" : _toAmount!.toStringAsFixed(8);
|
||||
|
||||
String get rateDisplayString {
|
||||
if (rate == null || from == null || to == null) {
|
||||
return "N/A";
|
||||
} else {
|
||||
return "1 ${from!.ticker.toUpperCase()} ~${rate!.toStringAsFixed(8)} ${to!.ticker.toUpperCase()}";
|
||||
}
|
||||
}
|
||||
|
||||
bool get canExchange {
|
||||
return _fromAmount != null &&
|
||||
_fromAmount != Decimal.zero &&
|
||||
_toAmount != null &&
|
||||
rate != null &&
|
||||
minimumSendWarning.isEmpty;
|
||||
}
|
||||
|
||||
String get minimumSendWarning {
|
||||
if (_from != null &&
|
||||
_fromAmount != null &&
|
||||
_minFromAmount != null &&
|
||||
_fromAmount! < _minFromAmount!) {
|
||||
return "Minimum amount ${_minFromAmount!.toString()} ${from!.ticker.toUpperCase()}";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
Future<void> init(Currency? from, Currency? to) async {
|
||||
_from = from;
|
||||
_to = to;
|
||||
|
@ -43,10 +81,6 @@ class EstimatedRateExchangeFormState extends ChangeNotifier {
|
|||
final Decimal? newMinFromAmount = _minToAmount;
|
||||
final Decimal? newMinToAmount = _minFromAmount;
|
||||
|
||||
// final Decimal? newRate = rate == null
|
||||
// ? rate
|
||||
// : (Decimal.one / rate!).toDecimal(scaleOnInfinitePrecision: 12);
|
||||
|
||||
final Currency? newTo = from;
|
||||
final Currency? newFrom = to;
|
||||
|
||||
|
@ -63,48 +97,11 @@ class EstimatedRateExchangeFormState extends ChangeNotifier {
|
|||
|
||||
await _updateMinFromAmount(shouldNotifyListeners: false);
|
||||
|
||||
rate = null;
|
||||
|
||||
if (_fromAmount != null) {
|
||||
Decimal? amt;
|
||||
if (_minFromAmount != null) {
|
||||
if (_minFromAmount! > _fromAmount!) {
|
||||
amt = await getStandardEstimatedToAmount(
|
||||
fromAmount: _minFromAmount!, from: _from!, to: _to!);
|
||||
if (amt != null) {
|
||||
rate =
|
||||
(amt / _minFromAmount!).toDecimal(scaleOnInfinitePrecision: 12);
|
||||
}
|
||||
} else {
|
||||
amt = await getStandardEstimatedToAmount(
|
||||
fromAmount: _fromAmount!, from: _from!, to: _to!);
|
||||
if (amt != null) {
|
||||
rate = (amt / _fromAmount!).toDecimal(scaleOnInfinitePrecision: 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rate != null) {
|
||||
_toAmount = (_fromAmount! * rate!);
|
||||
}
|
||||
} else {
|
||||
if (_minFromAmount != null) {
|
||||
Decimal? amt = await getStandardEstimatedToAmount(
|
||||
fromAmount: _minFromAmount!, from: _from!, to: _to!);
|
||||
if (amt != null) {
|
||||
rate =
|
||||
(amt / _minFromAmount!).toDecimal(scaleOnInfinitePrecision: 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
await updateRate();
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String get fromAmountString =>
|
||||
_fromAmount == null ? "" : _fromAmount!.toStringAsFixed(8);
|
||||
String get toAmountString =>
|
||||
_toAmount == null ? "" : _toAmount!.toStringAsFixed(8);
|
||||
|
||||
Future<void> updateTo(Currency to, bool shouldNotifyListeners) async {
|
||||
try {
|
||||
_to = to;
|
||||
|
@ -115,46 +112,8 @@ class EstimatedRateExchangeFormState extends ChangeNotifier {
|
|||
}
|
||||
|
||||
await _updateMinFromAmount(shouldNotifyListeners: shouldNotifyListeners);
|
||||
// await _updateMinToAmount(shouldNotifyListeners: shouldNotifyListeners);
|
||||
|
||||
rate = null;
|
||||
|
||||
if (_fromAmount != null) {
|
||||
Decimal? amt;
|
||||
if (_minFromAmount != null) {
|
||||
if (_minFromAmount! > _fromAmount!) {
|
||||
amt = await getStandardEstimatedToAmount(
|
||||
fromAmount: _minFromAmount!, from: _from!, to: _to!);
|
||||
if (amt != null) {
|
||||
rate = (amt / _minFromAmount!)
|
||||
.toDecimal(scaleOnInfinitePrecision: 12);
|
||||
}
|
||||
debugPrint("A");
|
||||
} else {
|
||||
amt = await getStandardEstimatedToAmount(
|
||||
fromAmount: _fromAmount!, from: _from!, to: _to!);
|
||||
if (amt != null) {
|
||||
rate =
|
||||
(amt / _fromAmount!).toDecimal(scaleOnInfinitePrecision: 12);
|
||||
}
|
||||
debugPrint("B");
|
||||
}
|
||||
}
|
||||
if (rate != null) {
|
||||
_toAmount = (_fromAmount! * rate!);
|
||||
}
|
||||
debugPrint("C");
|
||||
} else {
|
||||
if (_minFromAmount != null) {
|
||||
Decimal? amt = await getStandardEstimatedToAmount(
|
||||
fromAmount: _minFromAmount!, from: _from!, to: _to!);
|
||||
if (amt != null) {
|
||||
rate =
|
||||
(amt / _minFromAmount!).toDecimal(scaleOnInfinitePrecision: 12);
|
||||
}
|
||||
debugPrint("D");
|
||||
}
|
||||
}
|
||||
await updateRate(shouldNotifyListeners: shouldNotifyListeners);
|
||||
|
||||
debugPrint(
|
||||
"_updated TO: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$_fromAmount _toAmount=$_toAmount rate:$rate");
|
||||
|
@ -163,7 +122,7 @@ class EstimatedRateExchangeFormState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Fatal);
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,40 +138,7 @@ class EstimatedRateExchangeFormState extends ChangeNotifier {
|
|||
|
||||
await _updateMinFromAmount(shouldNotifyListeners: shouldNotifyListeners);
|
||||
|
||||
rate = null;
|
||||
|
||||
if (_fromAmount != null) {
|
||||
Decimal? amt;
|
||||
if (_minFromAmount != null) {
|
||||
if (_minFromAmount! > _fromAmount!) {
|
||||
amt = await getStandardEstimatedToAmount(
|
||||
fromAmount: _minFromAmount!, from: _from!, to: _to!);
|
||||
if (amt != null) {
|
||||
rate = (amt / _minFromAmount!)
|
||||
.toDecimal(scaleOnInfinitePrecision: 12);
|
||||
}
|
||||
} else {
|
||||
amt = await getStandardEstimatedToAmount(
|
||||
fromAmount: _fromAmount!, from: _from!, to: _to!);
|
||||
if (amt != null) {
|
||||
rate =
|
||||
(amt / _fromAmount!).toDecimal(scaleOnInfinitePrecision: 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rate != null) {
|
||||
_toAmount = (_fromAmount! * rate!);
|
||||
}
|
||||
} else {
|
||||
if (_minFromAmount != null) {
|
||||
Decimal? amt = await getStandardEstimatedToAmount(
|
||||
fromAmount: _minFromAmount!, from: _from!, to: _to!);
|
||||
if (amt != null) {
|
||||
rate =
|
||||
(amt / _minFromAmount!).toDecimal(scaleOnInfinitePrecision: 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
await updateRate(shouldNotifyListeners: shouldNotifyListeners);
|
||||
|
||||
debugPrint(
|
||||
"_updated FROM: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$_fromAmount _toAmount=$_toAmount rate:$rate");
|
||||
|
@ -220,55 +146,10 @@ class EstimatedRateExchangeFormState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Fatal);
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
String get rateDisplayString {
|
||||
if (rate == null || from == null || to == null) {
|
||||
return "N/A";
|
||||
} else {
|
||||
return "1 ${from!.ticker.toUpperCase()} ~${rate!.toStringAsFixed(8)} ${to!.ticker.toUpperCase()}";
|
||||
}
|
||||
}
|
||||
|
||||
bool get canExchange {
|
||||
return _fromAmount != null &&
|
||||
_fromAmount != Decimal.zero &&
|
||||
_toAmount != null &&
|
||||
rate != null &&
|
||||
minimumReceiveWarning.isEmpty &&
|
||||
minimumSendWarning.isEmpty;
|
||||
}
|
||||
|
||||
String get minimumSendWarning {
|
||||
if (_from != null &&
|
||||
_fromAmount != null &&
|
||||
_minFromAmount != null &&
|
||||
_fromAmount! < _minFromAmount!) {
|
||||
return "Minimum amount ${_minFromAmount!.toString()} ${from!.ticker.toUpperCase()}";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
String get minimumReceiveWarning {
|
||||
// TODO not sure this is needed
|
||||
// if (_toAmount != null &&
|
||||
// _minToAmount != null &&
|
||||
// _toAmount! < _minToAmount!) {
|
||||
// return "Minimum amount ${_minToAmount!.toString()} ${to.ticker.toUpperCase()}";
|
||||
// }
|
||||
return "";
|
||||
}
|
||||
|
||||
// Future<void> _updateMinToAmount({required bool shouldNotifyListeners}) async {
|
||||
// _minToAmount = await getStandardMinExchangeAmount(from: to!, to: from!);
|
||||
// if (shouldNotifyListeners) {
|
||||
// notifyListeners();
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<void> _updateMinFromAmount(
|
||||
{required bool shouldNotifyListeners}) async {
|
||||
_minFromAmount = await getStandardMinExchangeAmount(from: from!, to: to!);
|
||||
|
@ -277,48 +158,32 @@ class EstimatedRateExchangeFormState extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> setToAmountAndCalculateFromAmount(
|
||||
Decimal newToAmount,
|
||||
bool shouldNotifyListeners,
|
||||
) async {
|
||||
// if (newToAmount == Decimal.zero) {
|
||||
// _fromAmount = Decimal.zero;
|
||||
// _toAmount = Decimal.zero;
|
||||
// if (shouldNotifyListeners) {
|
||||
// notifyListeners();
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (rate != null) {
|
||||
_fromAmount =
|
||||
(newToAmount / rate!).toDecimal(scaleOnInfinitePrecision: 12);
|
||||
}
|
||||
|
||||
_toAmount = newToAmount;
|
||||
if (shouldNotifyListeners) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
// Future<void> setToAmountAndCalculateFromAmount(
|
||||
// Decimal newToAmount,
|
||||
// bool shouldNotifyListeners,
|
||||
// ) async {
|
||||
// if (newToAmount == Decimal.zero) {
|
||||
// _fromAmount = Decimal.zero;
|
||||
// }
|
||||
//
|
||||
// _toAmount = newToAmount;
|
||||
// await updateRate();
|
||||
// if (shouldNotifyListeners) {
|
||||
// notifyListeners();
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<void> setFromAmountAndCalculateToAmount(
|
||||
Decimal newFromAmount,
|
||||
bool shouldNotifyListeners,
|
||||
) async {
|
||||
// if (newFromAmount == Decimal.zero) {
|
||||
// _fromAmount = Decimal.zero;
|
||||
// _toAmount = Decimal.zero;
|
||||
// if (shouldNotifyListeners) {
|
||||
// notifyListeners();
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (rate != null) {
|
||||
_toAmount = (newFromAmount * rate!);
|
||||
if (newFromAmount == Decimal.zero) {
|
||||
_toAmount = Decimal.zero;
|
||||
}
|
||||
|
||||
_fromAmount = newFromAmount;
|
||||
await updateRate(shouldNotifyListeners: shouldNotifyListeners);
|
||||
|
||||
if (shouldNotifyListeners) {
|
||||
notifyListeners();
|
||||
}
|
||||
|
@ -329,8 +194,12 @@ class EstimatedRateExchangeFormState extends ChangeNotifier {
|
|||
required Currency from,
|
||||
required Currency to,
|
||||
}) async {
|
||||
final response = await ChangeNow.instance.getEstimatedExchangeAmount(
|
||||
fromTicker: from.ticker, toTicker: to.ticker, fromAmount: fromAmount);
|
||||
final response =
|
||||
await (cnTesting ?? ChangeNow.instance).getEstimatedExchangeAmount(
|
||||
fromTicker: from.ticker,
|
||||
toTicker: to.ticker,
|
||||
fromAmount: fromAmount,
|
||||
);
|
||||
|
||||
if (response.value != null) {
|
||||
return response.value!.estimatedAmount;
|
||||
|
@ -341,11 +210,31 @@ class EstimatedRateExchangeFormState extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
// Future<Decimal?> getStandardEstimatedFromAmount({
|
||||
// required Decimal toAmount,
|
||||
// required Currency from,
|
||||
// required Currency to,
|
||||
// }) async {
|
||||
// final response = await (cnTesting ?? ChangeNow.instance)
|
||||
// .getEstimatedExchangeAmount(
|
||||
// fromTicker: from.ticker,
|
||||
// toTicker: to.ticker,
|
||||
// fromAmount: toAmount, );
|
||||
//
|
||||
// if (response.value != null) {
|
||||
// return response.value!.fromAmount;
|
||||
// } else {
|
||||
// _onError?.call(
|
||||
// "Failed to fetch estimated amount: ${response.exception?.toString()}");
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<Decimal?> getStandardMinExchangeAmount({
|
||||
required Currency from,
|
||||
required Currency to,
|
||||
}) async {
|
||||
final response = await ChangeNow.instance
|
||||
final response = await (cnTesting ?? ChangeNow.instance)
|
||||
.getMinimalExchangeAmount(fromTicker: from.ticker, toTicker: to.ticker);
|
||||
|
||||
if (response.value != null) {
|
||||
|
@ -357,8 +246,6 @@ class EstimatedRateExchangeFormState extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
void Function(String)? _onError;
|
||||
|
||||
void setOnError({
|
||||
required void Function(String)? onError,
|
||||
bool shouldNotifyListeners = false,
|
||||
|
@ -368,4 +255,28 @@ class EstimatedRateExchangeFormState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateRate({bool shouldNotifyListeners = false}) async {
|
||||
rate = null;
|
||||
final amount = _fromAmount;
|
||||
final minAmount = _minFromAmount;
|
||||
if (amount != null && amount > Decimal.zero) {
|
||||
Decimal? amt;
|
||||
if (minAmount != null) {
|
||||
if (minAmount <= amount) {
|
||||
amt = await getStandardEstimatedToAmount(
|
||||
fromAmount: amount, from: _from!, to: _to!);
|
||||
if (amt != null) {
|
||||
rate = (amt / amount).toDecimal(scaleOnInfinitePrecision: 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rate != null && amt != null) {
|
||||
_toAmount = amt;
|
||||
}
|
||||
}
|
||||
if (shouldNotifyListeners) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,44 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart';
|
||||
import 'package:stackwallet/services/change_now/change_now.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
||||
class FixedRateExchangeFormState extends ChangeNotifier {
|
||||
Decimal? _fromAmount;
|
||||
Decimal? _toAmount;
|
||||
|
||||
FixedRateMarket? _market;
|
||||
|
||||
FixedRateMarket? get market => _market;
|
||||
|
||||
CNExchangeEstimate? _estimate;
|
||||
CNExchangeEstimate? get estimate => _estimate;
|
||||
|
||||
Decimal? get rate {
|
||||
if (_estimate == null) {
|
||||
return null;
|
||||
} else {
|
||||
return (_estimate!.toAmount / _estimate!.fromAmount)
|
||||
.toDecimal(scaleOnInfinitePrecision: 12);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> swap(FixedRateMarket reverseFixedRateMarket) async {
|
||||
final Decimal? tmp = _fromAmount;
|
||||
_fromAmount = _toAmount;
|
||||
_toAmount = tmp;
|
||||
|
||||
await updateMarket(reverseFixedRateMarket, true);
|
||||
await updateMarket(reverseFixedRateMarket, false);
|
||||
await updateRateEstimate(CNEstimateType.direct);
|
||||
_toAmount = _estimate?.toAmount ?? Decimal.zero;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String get fromAmountString =>
|
||||
_fromAmount == null ? "-" : _fromAmount!.toStringAsFixed(8);
|
||||
_fromAmount == null ? "" : _fromAmount!.toStringAsFixed(8);
|
||||
String get toAmountString =>
|
||||
_toAmount == null ? "-" : _toAmount!.toStringAsFixed(8);
|
||||
_toAmount == null ? "" : _toAmount!.toStringAsFixed(8);
|
||||
|
||||
Future<void> updateMarket(
|
||||
FixedRateMarket? market,
|
||||
|
@ -37,7 +54,7 @@ class FixedRateExchangeFormState extends ChangeNotifier {
|
|||
if (_fromAmount! <= Decimal.zero) {
|
||||
_toAmount = Decimal.zero;
|
||||
} else {
|
||||
_toAmount = (_fromAmount! * _market!.rate) - _market!.minerFee;
|
||||
await updateRateEstimate(CNEstimateType.direct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,10 +65,10 @@ class FixedRateExchangeFormState extends ChangeNotifier {
|
|||
}
|
||||
|
||||
String get rateDisplayString {
|
||||
if (_market == null) {
|
||||
if (_market == null || _estimate == null) {
|
||||
return "N/A";
|
||||
} else {
|
||||
return "1 ${_market!.from.toUpperCase()} ~${_market!.rate.toStringAsFixed(8)} ${_market!.to.toUpperCase()}";
|
||||
return "1 ${_estimate!.fromCurrency.toUpperCase()} ~${rate!.toStringAsFixed(8)} ${_estimate!.toCurrency.toUpperCase()}";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,14 +95,10 @@ class FixedRateExchangeFormState extends ChangeNotifier {
|
|||
Decimal newToAmount,
|
||||
bool shouldNotifyListeners,
|
||||
) async {
|
||||
if (_market != null) {
|
||||
_fromAmount = (newToAmount / _market!.rate)
|
||||
.toDecimal(scaleOnInfinitePrecision: 12) +
|
||||
_market!.minerFee;
|
||||
}
|
||||
|
||||
_toAmount = newToAmount;
|
||||
|
||||
if (shouldNotifyListeners) {
|
||||
await updateRateEstimate(CNEstimateType.reverse);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
@ -94,12 +107,10 @@ class FixedRateExchangeFormState extends ChangeNotifier {
|
|||
Decimal newFromAmount,
|
||||
bool shouldNotifyListeners,
|
||||
) async {
|
||||
if (_market != null) {
|
||||
_toAmount = (newFromAmount * _market!.rate) - _market!.minerFee;
|
||||
}
|
||||
|
||||
_fromAmount = newFromAmount;
|
||||
|
||||
if (shouldNotifyListeners) {
|
||||
await updateRateEstimate(CNEstimateType.direct);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
@ -115,4 +126,53 @@ class FixedRateExchangeFormState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateRateEstimate(CNEstimateType direction) async {
|
||||
if (market != null) {
|
||||
Decimal? amount;
|
||||
// set amount based on trade estimate direction
|
||||
switch (direction) {
|
||||
case CNEstimateType.direct:
|
||||
if (_fromAmount != null
|
||||
// &&
|
||||
// market!.min >= _fromAmount! &&
|
||||
// _fromAmount! <= market!.max
|
||||
) {
|
||||
amount = _fromAmount!;
|
||||
}
|
||||
break;
|
||||
case CNEstimateType.reverse:
|
||||
if (_toAmount != null
|
||||
// &&
|
||||
// market!.min >= _toAmount! &&
|
||||
// _toAmount! <= market!.max
|
||||
) {
|
||||
amount = _toAmount!;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (amount != null && market != null && amount > Decimal.zero) {
|
||||
final response = await ChangeNow.instance.getEstimatedExchangeAmountV2(
|
||||
fromTicker: market!.from,
|
||||
toTicker: market!.to,
|
||||
fromOrTo: direction,
|
||||
flow: CNFlowType.fixedRate,
|
||||
amount: amount,
|
||||
);
|
||||
|
||||
if (response.value != null) {
|
||||
// update estimate if response succeeded
|
||||
_estimate = response.value;
|
||||
|
||||
_toAmount = _estimate?.toAmount;
|
||||
_fromAmount = _estimate?.fromAmount;
|
||||
notifyListeners();
|
||||
} else if (response.exception != null) {
|
||||
Logging.instance.log("updateRateEstimate(): ${response.exception}",
|
||||
level: LogLevel.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -164,7 +164,7 @@ class _Step4ViewState extends ConsumerState<Step4View> {
|
|||
height: 8,
|
||||
),
|
||||
Text(
|
||||
"Send FIRO to the address below. Once it is received, ChangeNOW will send the BTC to the recipient address you provided. You can find this trade details and check its status in the list of trades.",
|
||||
"Send ${model.sendTicker} to the address below. Once it is received, ChangeNOW will send the ${model.receiveTicker} to the recipient address you provided. You can find this trade details and check its status in the list of trades.",
|
||||
style: STextStyles.itemSubtitle,
|
||||
),
|
||||
const SizedBox(
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/currency.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart';
|
||||
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
|
||||
|
@ -231,6 +232,65 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
|
|||
? ref.read(estimatedRateExchangeFormProvider).toAmountString
|
||||
: ref.read(fixedRateExchangeFormProvider).toAmountString;
|
||||
|
||||
_sendFocusNode.addListener(() async {
|
||||
if (!_sendFocusNode.hasFocus) {
|
||||
final newFromAmount = Decimal.tryParse(_sendController.text);
|
||||
if (newFromAmount != null) {
|
||||
if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
|
||||
ExchangeRateType.estimated) {
|
||||
await ref
|
||||
.read(estimatedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(newFromAmount, true);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(newFromAmount, true);
|
||||
}
|
||||
} else {
|
||||
if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
|
||||
ExchangeRateType.estimated) {
|
||||
await ref
|
||||
.read(estimatedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(Decimal.zero, true);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(Decimal.zero, true);
|
||||
}
|
||||
_receiveController.text = "";
|
||||
}
|
||||
}
|
||||
});
|
||||
_receiveFocusNode.addListener(() async {
|
||||
if (!_receiveFocusNode.hasFocus) {
|
||||
final newToAmount = Decimal.tryParse(_receiveController.text);
|
||||
if (newToAmount != null) {
|
||||
if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
|
||||
ExchangeRateType.estimated) {
|
||||
// await ref
|
||||
// .read(estimatedRateExchangeFormProvider)
|
||||
// .setToAmountAndCalculateFromAmount(newToAmount, true);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setToAmountAndCalculateFromAmount(newToAmount, true);
|
||||
}
|
||||
} else {
|
||||
if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
|
||||
ExchangeRateType.estimated) {
|
||||
// await ref
|
||||
// .read(estimatedRateExchangeFormProvider)
|
||||
// .setToAmountAndCalculateFromAmount(Decimal.zero, true);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setToAmountAndCalculateFromAmount(Decimal.zero, true);
|
||||
}
|
||||
_sendController.text = "";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -332,12 +392,12 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
|
|||
await ref
|
||||
.read(estimatedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(
|
||||
newFromAmount, true);
|
||||
newFromAmount, false);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(
|
||||
newFromAmount, true);
|
||||
newFromAmount, false);
|
||||
}
|
||||
} else {
|
||||
if (ref
|
||||
|
@ -347,12 +407,12 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
|
|||
await ref
|
||||
.read(estimatedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(
|
||||
Decimal.zero, true);
|
||||
Decimal.zero, false);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(
|
||||
Decimal.zero, true);
|
||||
Decimal.zero, false);
|
||||
}
|
||||
_receiveController.text = "";
|
||||
}
|
||||
|
@ -631,6 +691,10 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
|
|||
TextFormField(
|
||||
focusNode: _receiveFocusNode,
|
||||
controller: _receiveController,
|
||||
readOnly: ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.exchangeRateType ==
|
||||
ExchangeRateType.estimated,
|
||||
onTap: () {
|
||||
if (_receiveController.text == "-") {
|
||||
_receiveController.text = "";
|
||||
|
@ -643,30 +707,30 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
|
|||
.read(prefsChangeNotifierProvider)
|
||||
.exchangeRateType ==
|
||||
ExchangeRateType.estimated) {
|
||||
await ref
|
||||
.read(estimatedRateExchangeFormProvider)
|
||||
.setToAmountAndCalculateFromAmount(
|
||||
newToAmount, true);
|
||||
// await ref
|
||||
// .read(estimatedRateExchangeFormProvider)
|
||||
// .setToAmountAndCalculateFromAmount(
|
||||
// newToAmount, false);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setToAmountAndCalculateFromAmount(
|
||||
newToAmount, true);
|
||||
newToAmount, false);
|
||||
}
|
||||
} else {
|
||||
if (ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.exchangeRateType ==
|
||||
ExchangeRateType.estimated) {
|
||||
await ref
|
||||
.read(estimatedRateExchangeFormProvider)
|
||||
.setToAmountAndCalculateFromAmount(
|
||||
Decimal.zero, true);
|
||||
// await ref
|
||||
// .read(estimatedRateExchangeFormProvider)
|
||||
// .setToAmountAndCalculateFromAmount(
|
||||
// Decimal.zero, false);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setToAmountAndCalculateFromAmount(
|
||||
Decimal.zero, true);
|
||||
Decimal.zero, false);
|
||||
}
|
||||
_sendController.text = "";
|
||||
}
|
||||
|
@ -948,6 +1012,24 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
|
|||
final to = availableCurrencies.firstWhere(
|
||||
(e) => e.ticker == toTicker);
|
||||
|
||||
final newFromAmount =
|
||||
Decimal.tryParse(_sendController.text);
|
||||
if (newFromAmount != null) {
|
||||
await ref
|
||||
.read(
|
||||
estimatedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(
|
||||
newFromAmount, false);
|
||||
} else {
|
||||
await ref
|
||||
.read(
|
||||
estimatedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(
|
||||
Decimal.zero, false);
|
||||
|
||||
_receiveController.text = "";
|
||||
}
|
||||
|
||||
await ref
|
||||
.read(estimatedRateExchangeFormProvider)
|
||||
.updateTo(to, false);
|
||||
|
@ -992,6 +1074,23 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
|
|||
} catch (_) {
|
||||
market = null;
|
||||
}
|
||||
|
||||
final newFromAmount =
|
||||
Decimal.tryParse(_sendController.text);
|
||||
if (newFromAmount != null) {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(
|
||||
newFromAmount, false);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(
|
||||
Decimal.zero, false);
|
||||
|
||||
_receiveController.text = "";
|
||||
}
|
||||
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.updateMarket(market, false);
|
||||
|
@ -1170,11 +1269,12 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
|
|||
|
||||
final response = await ref
|
||||
.read(changeNowProvider)
|
||||
.getEstimatedFixedRateExchangeAmount(
|
||||
.getEstimatedExchangeAmountV2(
|
||||
fromTicker: fromTicker,
|
||||
toTicker: toTicker,
|
||||
fromAmount: sendAmount,
|
||||
useRateId: true,
|
||||
fromOrTo: CNEstimateType.direct,
|
||||
amount: sendAmount,
|
||||
flow: CNFlowType.fixedRate,
|
||||
);
|
||||
|
||||
bool? shouldCancel;
|
||||
|
@ -1251,15 +1351,14 @@ class _ExchangeViewState extends ConsumerState<ExchangeView> {
|
|||
}
|
||||
|
||||
String rate =
|
||||
"1 $fromTicker ~${ref.read(fixedRateExchangeFormProvider).market!.rate.toStringAsFixed(8)} $toTicker";
|
||||
"1 $fromTicker ~${ref.read(fixedRateExchangeFormProvider).rate!.toStringAsFixed(8)} $toTicker";
|
||||
|
||||
final model = IncompleteExchangeModel(
|
||||
sendTicker: fromTicker,
|
||||
receiveTicker: toTicker,
|
||||
rateInfo: rate,
|
||||
sendAmount: sendAmount,
|
||||
receiveAmount:
|
||||
response.value!.estimatedAmount,
|
||||
receiveAmount: response.value!.toAmount,
|
||||
rateId: response.value!.rateId,
|
||||
rateType: rateType,
|
||||
);
|
||||
|
|
|
@ -55,7 +55,7 @@ class StepRow extends StatelessWidget {
|
|||
));
|
||||
}
|
||||
list.add(StepIndicator(
|
||||
step: count - 1,
|
||||
step: count,
|
||||
status: getStatus(count - 1),
|
||||
));
|
||||
return list;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
@ -10,6 +12,7 @@ import 'package:stackwallet/notifications/show_flush_bar.dart';
|
|||
import 'package:stackwallet/pages/exchange_view/edit_trade_note_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||
import 'package:stackwallet/providers/exchange/change_now_provider.dart';
|
||||
import 'package:stackwallet/providers/exchange/trade_note_service_provider.dart';
|
||||
import 'package:stackwallet/providers/global/trades_service_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
|
@ -63,6 +66,26 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
clipboard = widget.clipboard;
|
||||
transactionIfSentFromStack = widget.transactionIfSentFromStack;
|
||||
walletId = widget.walletId;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
final trade = ref
|
||||
.read(tradesServiceProvider)
|
||||
.trades
|
||||
.firstWhere((e) => e.id == tradeId);
|
||||
|
||||
if (mounted && trade.statusObject == null ||
|
||||
trade.statusObject!.amountSendDecimal.isEmpty) {
|
||||
final status = await ref
|
||||
.read(changeNowProvider)
|
||||
.getTransactionStatus(id: trade.id);
|
||||
|
||||
if (mounted && status.value != null) {
|
||||
await ref.read(tradesServiceProvider).edit(
|
||||
trade: trade.copyWith(statusObject: status.value),
|
||||
shouldNotifyListeners: true);
|
||||
}
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -77,8 +100,6 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
status = ChangeNowTransactionStatus.Failed;
|
||||
}
|
||||
|
||||
debugPrint("statusstatusstatusstatus: $status");
|
||||
debugPrint("statusstatusstatusstatusSTRING: $statusString");
|
||||
switch (status) {
|
||||
case ChangeNowTransactionStatus.New:
|
||||
case ChangeNowTransactionStatus.Waiting:
|
||||
|
@ -113,6 +134,11 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
debugPrint("hasTx: $hasTx");
|
||||
debugPrint("trade: ${trade.toString()}");
|
||||
|
||||
final sendAmount = Decimal.tryParse(
|
||||
trade.statusObject?.amountSendDecimal ?? "") ??
|
||||
Decimal.tryParse(trade.statusObject?.expectedSendAmountDecimal ?? "") ??
|
||||
Decimal.parse("-1");
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: CFColors.almostWhite,
|
||||
appBar: AppBar(
|
||||
|
@ -150,7 +176,7 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
height: 4,
|
||||
),
|
||||
SelectableText(
|
||||
"${Format.localizedStringAsFixed(value: Decimal.parse(trade.statusObject?.amountSendDecimal ?? trade.amount), locale: ref.watch(
|
||||
"${Format.localizedStringAsFixed(value: sendAmount, locale: ref.watch(
|
||||
localeServiceChangeNotifierProvider
|
||||
.select((value) => value.locale),
|
||||
), decimalPlaces: trade.fromCurrency.toLowerCase() == "xmr" ? 12 : 8)} ${trade.fromCurrency.toUpperCase()}",
|
||||
|
@ -205,7 +231,7 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
],
|
||||
),
|
||||
),
|
||||
if (!sentFromStack && hasTx)
|
||||
if (!sentFromStack && !hasTx)
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
|
@ -214,9 +240,8 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
color: CFColors.warningBackground,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: "You must send at least ${Decimal.parse(
|
||||
trade.statusObject!.amountSendDecimal,
|
||||
).toStringAsFixed(
|
||||
text:
|
||||
"You must send at least ${sendAmount.toStringAsFixed(
|
||||
trade.fromCurrency.toLowerCase() == "xmr" ? 12 : 8,
|
||||
)} ${trade.fromCurrency.toUpperCase()}. ",
|
||||
style: STextStyles.label.copyWith(
|
||||
|
@ -225,9 +250,8 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "If you send less than ${Decimal.parse(
|
||||
trade.statusObject!.amountSendDecimal,
|
||||
).toStringAsFixed(
|
||||
text:
|
||||
"If you send less than ${sendAmount.toStringAsFixed(
|
||||
trade.fromCurrency.toLowerCase() == "xmr"
|
||||
? 12
|
||||
: 8,
|
||||
|
@ -623,11 +647,11 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
onTap: () async {
|
||||
final data = ClipboardData(text: trade.id);
|
||||
await clipboard.setData(data);
|
||||
showFloatingFlushBar(
|
||||
unawaited(showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
context: context,
|
||||
);
|
||||
));
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/currency.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart';
|
||||
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
|
||||
|
@ -243,6 +244,64 @@ class _WalletInitiatedExchangeViewState
|
|||
ref.read(estimatedRateExchangeFormProvider).clearAmounts(true);
|
||||
// ref.read(fixedRateExchangeFormProvider);
|
||||
});
|
||||
_sendFocusNode.addListener(() async {
|
||||
if (!_sendFocusNode.hasFocus) {
|
||||
final newFromAmount = Decimal.tryParse(_sendController.text);
|
||||
if (newFromAmount != null) {
|
||||
if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
|
||||
ExchangeRateType.estimated) {
|
||||
await ref
|
||||
.read(estimatedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(newFromAmount, true);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(newFromAmount, true);
|
||||
}
|
||||
} else {
|
||||
if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
|
||||
ExchangeRateType.estimated) {
|
||||
await ref
|
||||
.read(estimatedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(Decimal.zero, true);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(Decimal.zero, true);
|
||||
}
|
||||
_receiveController.text = "";
|
||||
}
|
||||
}
|
||||
});
|
||||
_receiveFocusNode.addListener(() async {
|
||||
if (!_receiveFocusNode.hasFocus) {
|
||||
final newToAmount = Decimal.tryParse(_receiveController.text);
|
||||
if (newToAmount != null) {
|
||||
if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
|
||||
ExchangeRateType.estimated) {
|
||||
// await ref
|
||||
// .read(estimatedRateExchangeFormProvider)
|
||||
// .setToAmountAndCalculateFromAmount(newToAmount, true);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setToAmountAndCalculateFromAmount(newToAmount, true);
|
||||
}
|
||||
} else {
|
||||
if (ref.read(prefsChangeNotifierProvider).exchangeRateType ==
|
||||
ExchangeRateType.estimated) {
|
||||
// await ref
|
||||
// .read(estimatedRateExchangeFormProvider)
|
||||
// .setToAmountAndCalculateFromAmount(Decimal.zero, true);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setToAmountAndCalculateFromAmount(Decimal.zero, true);
|
||||
}
|
||||
_sendController.text = "";
|
||||
}
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -379,12 +438,12 @@ class _WalletInitiatedExchangeViewState
|
|||
await ref
|
||||
.read(estimatedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(
|
||||
newFromAmount, true);
|
||||
newFromAmount, false);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(
|
||||
newFromAmount, true);
|
||||
newFromAmount, false);
|
||||
}
|
||||
} else {
|
||||
if (ref
|
||||
|
@ -394,12 +453,12 @@ class _WalletInitiatedExchangeViewState
|
|||
await ref
|
||||
.read(estimatedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(
|
||||
Decimal.zero, true);
|
||||
Decimal.zero, false);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setFromAmountAndCalculateToAmount(
|
||||
Decimal.zero, true);
|
||||
Decimal.zero, false);
|
||||
}
|
||||
_receiveController.text = "";
|
||||
}
|
||||
|
@ -716,6 +775,10 @@ class _WalletInitiatedExchangeViewState
|
|||
TextFormField(
|
||||
focusNode: _receiveFocusNode,
|
||||
controller: _receiveController,
|
||||
readOnly: ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.exchangeRateType ==
|
||||
ExchangeRateType.estimated,
|
||||
onTap: () {
|
||||
if (_receiveController.text == "-") {
|
||||
_receiveController.text = "";
|
||||
|
@ -728,30 +791,30 @@ class _WalletInitiatedExchangeViewState
|
|||
.read(prefsChangeNotifierProvider)
|
||||
.exchangeRateType ==
|
||||
ExchangeRateType.estimated) {
|
||||
await ref
|
||||
.read(estimatedRateExchangeFormProvider)
|
||||
.setToAmountAndCalculateFromAmount(
|
||||
newToAmount, true);
|
||||
// await ref
|
||||
// .read(estimatedRateExchangeFormProvider)
|
||||
// .setToAmountAndCalculateFromAmount(
|
||||
// newToAmount, false);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setToAmountAndCalculateFromAmount(
|
||||
newToAmount, true);
|
||||
newToAmount, false);
|
||||
}
|
||||
} else {
|
||||
if (ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.exchangeRateType ==
|
||||
ExchangeRateType.estimated) {
|
||||
await ref
|
||||
.read(estimatedRateExchangeFormProvider)
|
||||
.setToAmountAndCalculateFromAmount(
|
||||
Decimal.zero, true);
|
||||
// await ref
|
||||
// .read(estimatedRateExchangeFormProvider)
|
||||
// .setToAmountAndCalculateFromAmount(
|
||||
// Decimal.zero, false);
|
||||
} else {
|
||||
await ref
|
||||
.read(fixedRateExchangeFormProvider)
|
||||
.setToAmountAndCalculateFromAmount(
|
||||
Decimal.zero, true);
|
||||
Decimal.zero, false);
|
||||
}
|
||||
_sendController.text = "";
|
||||
}
|
||||
|
@ -1374,11 +1437,12 @@ class _WalletInitiatedExchangeViewState
|
|||
|
||||
final response = await ref
|
||||
.read(changeNowProvider)
|
||||
.getEstimatedFixedRateExchangeAmount(
|
||||
.getEstimatedExchangeAmountV2(
|
||||
fromTicker: fromTicker,
|
||||
toTicker: toTicker,
|
||||
fromAmount: sendAmount,
|
||||
useRateId: true,
|
||||
fromOrTo: CNEstimateType.direct,
|
||||
amount: sendAmount,
|
||||
flow: CNFlowType.fixedRate,
|
||||
);
|
||||
|
||||
bool? shouldCancel;
|
||||
|
@ -1456,15 +1520,14 @@ class _WalletInitiatedExchangeViewState
|
|||
}
|
||||
|
||||
String rate =
|
||||
"1 $fromTicker ~${ref.read(fixedRateExchangeFormProvider).market!.rate.toStringAsFixed(8)} $toTicker";
|
||||
"1 $fromTicker ~${ref.read(fixedRateExchangeFormProvider).rate!.toStringAsFixed(8)} $toTicker";
|
||||
|
||||
final model = IncompleteExchangeModel(
|
||||
sendTicker: fromTicker,
|
||||
receiveTicker: toTicker,
|
||||
rateInfo: rate,
|
||||
sendAmount: sendAmount,
|
||||
receiveAmount:
|
||||
response.value!.estimatedAmount,
|
||||
receiveAmount: response.value!.toAmount,
|
||||
rateId: response.value!.rateId,
|
||||
rateType: rateType,
|
||||
);
|
||||
|
|
|
@ -6,10 +6,12 @@ import 'dart:ui' as ui;
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_svg/svg.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/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/cfcolors.dart';
|
||||
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
|
@ -318,8 +320,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
|||
child: QrImage(
|
||||
data: uriString,
|
||||
size: width,
|
||||
backgroundColor:
|
||||
CFColors.almostWhite,
|
||||
backgroundColor: CFColors.white,
|
||||
foregroundColor:
|
||||
CFColors.stackAccent,
|
||||
),
|
||||
|
@ -344,12 +345,40 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
|||
CFColors.buttonGray,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"Share",
|
||||
style:
|
||||
STextStyles.button.copyWith(
|
||||
color: CFColors.stackAccent,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center,
|
||||
children: [
|
||||
Center(
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.share,
|
||||
width: 14,
|
||||
height: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Text(
|
||||
"Share",
|
||||
textAlign:
|
||||
TextAlign.center,
|
||||
style: STextStyles.button
|
||||
.copyWith(
|
||||
color: CFColors
|
||||
.stackAccent,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,36 +1,114 @@
|
|||
import 'dart:async';
|
||||
|
||||
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:qr_flutter/qr_flutter.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/cfcolors.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/flush_bar_type.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.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/rounded_white_container.dart';
|
||||
|
||||
class ReceiveView extends StatelessWidget {
|
||||
class ReceiveView extends ConsumerStatefulWidget {
|
||||
const ReceiveView({
|
||||
Key? key,
|
||||
required this.coin,
|
||||
required this.receivingAddress,
|
||||
required this.walletId,
|
||||
this.clipboard = const ClipboardWrapper(),
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/receiveView";
|
||||
|
||||
final Coin coin;
|
||||
final String receivingAddress;
|
||||
final String walletId;
|
||||
final ClipboardInterface clipboard;
|
||||
|
||||
@override
|
||||
ConsumerState<ReceiveView> createState() => _ReceiveViewState();
|
||||
}
|
||||
|
||||
class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
||||
late final Coin coin;
|
||||
late final String walletId;
|
||||
late final ClipboardInterface clipboard;
|
||||
|
||||
Future<void> generateNewAddress() async {
|
||||
bool shouldPop = false;
|
||||
unawaited(
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => shouldPop,
|
||||
child: const CustomLoadingOverlay(
|
||||
message: "Generating address",
|
||||
eventBus: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(walletId)
|
||||
.generateNewAddress();
|
||||
|
||||
shouldPop = true;
|
||||
|
||||
if (mounted) {
|
||||
Navigator.of(context)
|
||||
.popUntil(ModalRoute.withName(ReceiveView.routeName));
|
||||
}
|
||||
}
|
||||
|
||||
String receivingAddress = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
walletId = widget.walletId;
|
||||
coin = widget.coin;
|
||||
clipboard = widget.clipboard;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
final address = await ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(walletId)
|
||||
.currentReceivingAddress;
|
||||
setState(() {
|
||||
receivingAddress = address;
|
||||
});
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
ref.listen(
|
||||
ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManagerProvider(walletId)
|
||||
.select((value) => value.currentReceivingAddress),
|
||||
(previous, next) {
|
||||
if (next is Future<String>) {
|
||||
next.then((value) => setState(() => receivingAddress = value));
|
||||
}
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: CFColors.almostWhite,
|
||||
appBar: AppBar(
|
||||
|
@ -52,15 +130,19 @@ class ReceiveView extends StatelessWidget {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: CFColors.white,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
clipboard.setData(
|
||||
ClipboardData(text: receivingAddress),
|
||||
);
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
iconAsset: Assets.svg.copy,
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
|
@ -70,35 +152,22 @@ class ReceiveView extends StatelessWidget {
|
|||
style: STextStyles.itemSubtitle,
|
||||
),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
clipboard.setData(
|
||||
ClipboardData(text: receivingAddress),
|
||||
);
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
iconAsset: Assets.svg.copy,
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
width: 10,
|
||||
height: 10,
|
||||
color: CFColors.link2,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
Text(
|
||||
"Copy",
|
||||
style: STextStyles.link2,
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
width: 10,
|
||||
height: 10,
|
||||
color: CFColors.link2,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
Text(
|
||||
"Copy",
|
||||
style: STextStyles.link2,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -119,47 +188,62 @@ class ReceiveView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
Center(
|
||||
child: QrImage(
|
||||
data: "${coin.uriScheme}:$receivingAddress",
|
||||
size: MediaQuery.of(context).size.width / 2,
|
||||
foregroundColor: CFColors.stackAccent,
|
||||
if (coin != Coin.epicCash)
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
// Spacer(
|
||||
// flex: 7,
|
||||
// ),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute:
|
||||
RouteGenerator.useMaterialPageRoute,
|
||||
builder: (_) => GenerateUriQrCodeView(
|
||||
coin: coin,
|
||||
receivingAddress: receivingAddress,
|
||||
),
|
||||
settings: const RouteSettings(
|
||||
name: GenerateUriQrCodeView.routeName,
|
||||
),
|
||||
if (coin != Coin.epicCash)
|
||||
TextButton(
|
||||
onPressed: generateNewAddress,
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all<Color>(
|
||||
CFColors.buttonGray,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"Generate new address",
|
||||
style: STextStyles.button.copyWith(
|
||||
color: CFColors.stackAccent,
|
||||
),
|
||||
);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all<Color>(
|
||||
CFColors.buttonGray,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"Generate QR Code",
|
||||
style: STextStyles.button.copyWith(
|
||||
color: CFColors.stackAccent,
|
||||
const SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
QrImage(
|
||||
data: "${coin.uriScheme}:$receivingAddress",
|
||||
size: MediaQuery.of(context).size.width / 2,
|
||||
foregroundColor: CFColors.stackAccent,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
BlueTextButton(
|
||||
text: "Create new QR code",
|
||||
onTap: () async {
|
||||
unawaited(Navigator.of(context).push(
|
||||
RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute:
|
||||
RouteGenerator.useMaterialPageRoute,
|
||||
builder: (_) => GenerateUriQrCodeView(
|
||||
coin: coin,
|
||||
receivingAddress: receivingAddress,
|
||||
),
|
||||
settings: const RouteSettings(
|
||||
name: GenerateUriQrCodeView.routeName,
|
||||
),
|
||||
),
|
||||
));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
|||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||
import 'package:stackwallet/utilities/cfcolors.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
|
||||
|
@ -19,6 +20,8 @@ import 'package:stackwallet/widgets/rounded_container.dart';
|
|||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
|
||||
import '../../providers/wallet/public_private_balance_state_provider.dart';
|
||||
|
||||
class ConfirmTransactionView extends ConsumerStatefulWidget {
|
||||
const ConfirmTransactionView({
|
||||
Key? key,
|
||||
|
@ -56,10 +59,20 @@ class _ConfirmTransactionViewState
|
|||
|
||||
final note = transactionInfo["note"] as String? ?? "";
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
|
||||
try {
|
||||
final txid = await manager.confirmSend(txData: transactionInfo);
|
||||
String txid;
|
||||
final coin = manager.coin;
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||
"Private") {
|
||||
txid = await (manager.wallet as FiroWallet)
|
||||
.confirmSendPublic(txData: transactionInfo);
|
||||
} else {
|
||||
txid = await manager.confirmSend(txData: transactionInfo);
|
||||
}
|
||||
|
||||
unawaited(manager.refresh());
|
||||
|
||||
// save note
|
||||
|
@ -79,7 +92,7 @@ class _ConfirmTransactionViewState
|
|||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message:
|
||||
"Connection failed. Please check the address and try again.",
|
||||
"Connection failed. Please check the address and try again.",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
|
@ -100,10 +113,10 @@ class _ConfirmTransactionViewState
|
|||
message: e.toString(),
|
||||
rightButton: TextButton(
|
||||
style: Theme.of(context).textButtonTheme.style?.copyWith(
|
||||
backgroundColor: MaterialStateProperty.all<Color>(
|
||||
CFColors.buttonGray,
|
||||
),
|
||||
),
|
||||
backgroundColor: MaterialStateProperty.all<Color>(
|
||||
CFColors.buttonGray,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"Ok",
|
||||
style: STextStyles.button.copyWith(
|
||||
|
@ -212,9 +225,9 @@ class _ConfirmTransactionViewState
|
|||
.select((value) => value.locale),
|
||||
),
|
||||
)} ${ref.watch(
|
||||
managerProvider
|
||||
.select((value) => value.coin),
|
||||
).ticker}",
|
||||
managerProvider
|
||||
.select((value) => value.coin),
|
||||
).ticker}",
|
||||
style: STextStyles.itemSubtitle12,
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
|
@ -240,9 +253,9 @@ class _ConfirmTransactionViewState
|
|||
.select((value) => value.locale),
|
||||
),
|
||||
)} ${ref.watch(
|
||||
managerProvider
|
||||
.select((value) => value.coin),
|
||||
).ticker}",
|
||||
managerProvider
|
||||
.select((value) => value.coin),
|
||||
).ticker}",
|
||||
style: STextStyles.itemSubtitle12,
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
|
@ -292,9 +305,9 @@ class _ConfirmTransactionViewState
|
|||
.select((value) => value.locale),
|
||||
),
|
||||
)} ${ref.watch(
|
||||
managerProvider
|
||||
.select((value) => value.coin),
|
||||
).ticker}",
|
||||
managerProvider
|
||||
.select((value) => value.coin),
|
||||
).ticker}",
|
||||
style: STextStyles.itemSubtitle12,
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
|
@ -306,18 +319,18 @@ class _ConfirmTransactionViewState
|
|||
),
|
||||
TextButton(
|
||||
style:
|
||||
Theme.of(context).textButtonTheme.style?.copyWith(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.all<Color>(
|
||||
CFColors.stackAccent,
|
||||
),
|
||||
),
|
||||
Theme.of(context).textButtonTheme.style?.copyWith(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.all<Color>(
|
||||
CFColors.stackAccent,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
final unlocked = await Navigator.push(
|
||||
context,
|
||||
RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute:
|
||||
RouteGenerator.useMaterialPageRoute,
|
||||
RouteGenerator.useMaterialPageRoute,
|
||||
builder: (_) => const LockscreenView(
|
||||
showBackButton: true,
|
||||
popOnSuccess: true,
|
||||
|
@ -325,9 +338,9 @@ class _ConfirmTransactionViewState
|
|||
routeOnSuccess: "",
|
||||
biometricsCancelButtonString: "CANCEL",
|
||||
biometricsLocalizedReason:
|
||||
"Authenticate to send transaction",
|
||||
"Authenticate to send transaction",
|
||||
biometricsAuthenticationTitle:
|
||||
"Confirm Transaction",
|
||||
"Confirm Transaction",
|
||||
),
|
||||
settings: const RouteSettings(
|
||||
name: "/confirmsendlockscreen"),
|
||||
|
|
|
@ -13,7 +13,9 @@ import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selectio
|
|||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
|
||||
import 'package:stackwallet/providers/ui/preview_tx_button_state_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||
import 'package:stackwallet/services/coins/manager.dart';
|
||||
import 'package:stackwallet/utilities/address_utils.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
|
@ -37,6 +39,8 @@ import 'package:stackwallet/widgets/stack_dialog.dart';
|
|||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||
|
||||
import 'sub_widgets/firo_balance_selection_sheet.dart';
|
||||
|
||||
class SendView extends ConsumerStatefulWidget {
|
||||
const SendView({
|
||||
Key? key,
|
||||
|
@ -82,6 +86,9 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
Decimal? _cachedAmountToSend;
|
||||
String? _address;
|
||||
|
||||
String? _privateBalanceString;
|
||||
String? _publicBalanceString;
|
||||
|
||||
bool _addressToggleFlag = false;
|
||||
|
||||
bool _cryptoAmountChangeLock = false;
|
||||
|
@ -160,13 +167,26 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
late Future<String> _calculateFeesFuture;
|
||||
|
||||
Map<int, String> cachedFees = {};
|
||||
Map<int, String> cachedFiroPrivateFees = {};
|
||||
Map<int, String> cachedFiroPublicFees = {};
|
||||
|
||||
Future<String> calculateFees(int amount) async {
|
||||
if (amount <= 0) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
if (cachedFees[amount] != null) {
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
if (ref.read(publicPrivateBalanceStateProvider.state).state ==
|
||||
"Private") {
|
||||
if (cachedFiroPrivateFees[amount] != null) {
|
||||
return cachedFiroPrivateFees[amount]!;
|
||||
}
|
||||
} else {
|
||||
if (cachedFiroPublicFees[amount] != null) {
|
||||
return cachedFiroPublicFees[amount]!;
|
||||
}
|
||||
}
|
||||
} else if (cachedFees[amount] != null) {
|
||||
return cachedFees[amount]!;
|
||||
}
|
||||
|
||||
|
@ -188,12 +208,53 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
break;
|
||||
}
|
||||
|
||||
final fee = await manager.estimateFeeFor(amount, feeRate);
|
||||
int fee;
|
||||
|
||||
cachedFees[amount] =
|
||||
Format.satoshisToAmount(fee).toStringAsFixed(Constants.decimalPlaces);
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
if (ref.read(publicPrivateBalanceStateProvider.state).state ==
|
||||
"Private") {
|
||||
fee = await manager.estimateFeeFor(amount, feeRate);
|
||||
|
||||
return cachedFees[amount]!;
|
||||
cachedFiroPrivateFees[amount] = Format.satoshisToAmount(fee)
|
||||
.toStringAsFixed(Constants.decimalPlaces);
|
||||
|
||||
return cachedFiroPrivateFees[amount]!;
|
||||
} else {
|
||||
fee = await (manager.wallet as FiroWallet)
|
||||
.estimateFeeForPublic(amount, feeRate);
|
||||
|
||||
cachedFiroPublicFees[amount] = Format.satoshisToAmount(fee)
|
||||
.toStringAsFixed(Constants.decimalPlaces);
|
||||
|
||||
return cachedFiroPublicFees[amount]!;
|
||||
}
|
||||
} else {
|
||||
fee = await manager.estimateFeeFor(amount, feeRate);
|
||||
cachedFees[amount] =
|
||||
Format.satoshisToAmount(fee).toStringAsFixed(Constants.decimalPlaces);
|
||||
|
||||
return cachedFees[amount]!;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _firoBalanceFuture(
|
||||
ChangeNotifierProvider<Manager> provider, String locale) async {
|
||||
final wallet = ref.read(provider).wallet as FiroWallet?;
|
||||
|
||||
if (wallet != null) {
|
||||
Decimal? balance;
|
||||
if (ref.read(publicPrivateBalanceStateProvider.state).state ==
|
||||
"Private") {
|
||||
balance = await wallet.availablePrivateBalance();
|
||||
} else {
|
||||
balance = await wallet.availablePublicBalance();
|
||||
}
|
||||
|
||||
return Format.localizedStringAsFixed(
|
||||
value: balance, locale: locale, decimalPlaces: 8);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -282,6 +343,22 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
.select((value) => value.getManagerProvider(walletId)));
|
||||
final String locale = ref.watch(
|
||||
localeServiceChangeNotifierProvider.select((value) => value.locale));
|
||||
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
ref.listen(publicPrivateBalanceStateProvider, (previous, next) {
|
||||
if (_amountToSend == null) {
|
||||
setState(() {
|
||||
_calculateFeesFuture = calculateFees(0);
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_calculateFeesFuture =
|
||||
calculateFees(Format.decimalAmountToSatoshis(_amountToSend!));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: CFColors.almostWhite,
|
||||
appBar: AppBar(
|
||||
|
@ -334,21 +411,59 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.iconFor(coin: coin),
|
||||
width: 18,
|
||||
height: 18,
|
||||
width: 22,
|
||||
height: 22,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 6,
|
||||
),
|
||||
Text(
|
||||
ref.watch(provider
|
||||
.select((value) => value.walletName)),
|
||||
style: STextStyles.titleBold12,
|
||||
),
|
||||
if (coin != Coin.firo &&
|
||||
coin != Coin.firoTestNet)
|
||||
Text(
|
||||
ref.watch(provider
|
||||
.select((value) => value.walletName)),
|
||||
style: STextStyles.titleBold12,
|
||||
),
|
||||
if (coin == Coin.firo ||
|
||||
coin == Coin.firoTestNet)
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
ref.watch(provider.select(
|
||||
(value) => value.walletName)),
|
||||
style: STextStyles.titleBold12
|
||||
.copyWith(fontSize: 14),
|
||||
),
|
||||
// const SizedBox(
|
||||
// height: 2,
|
||||
// ),
|
||||
Text(
|
||||
"${ref.watch(publicPrivateBalanceStateProvider.state).state} balance",
|
||||
style: STextStyles.label
|
||||
.copyWith(fontSize: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
FutureBuilder(
|
||||
future: ref.watch(provider.select(
|
||||
(value) => value.availableBalance)),
|
||||
future: (coin != Coin.firo &&
|
||||
coin != Coin.firoTestNet)
|
||||
? ref.watch(provider.select(
|
||||
(value) => value.availableBalance))
|
||||
: ref
|
||||
.watch(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state ==
|
||||
"Private"
|
||||
? (ref.watch(provider).wallet
|
||||
as FiroWallet)
|
||||
.availablePrivateBalance()
|
||||
: (ref.watch(provider).wallet
|
||||
as FiroWallet)
|
||||
.availablePublicBalance(),
|
||||
builder:
|
||||
(_, AsyncSnapshot<Decimal> snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
|
@ -423,6 +538,9 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
AnimatedText(
|
||||
stringsToLoopThrough: const [
|
||||
"Loading balance ",
|
||||
|
@ -730,6 +848,141 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
}
|
||||
},
|
||||
),
|
||||
if (coin == Coin.firo)
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
if (coin == Coin.firo)
|
||||
Text(
|
||||
"Send from",
|
||||
style: STextStyles.smallMed12,
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
if (coin == Coin.firo)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (coin == Coin.firo)
|
||||
Stack(
|
||||
children: [
|
||||
const TextField(
|
||||
readOnly: true,
|
||||
textInputAction: TextInputAction.none,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
child: RawMaterialButton(
|
||||
splashColor: CFColors.splashLight,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
showModalBottomSheet<dynamic>(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
builder: (_) => FiroBalanceSelectionSheet(
|
||||
walletId: walletId,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"${ref.watch(publicPrivateBalanceStateProvider.state).state} balance",
|
||||
style: STextStyles.itemSubtitle12,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _firoBalanceFuture(
|
||||
provider, locale),
|
||||
builder: (context,
|
||||
AsyncSnapshot<String?>
|
||||
snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
if (ref
|
||||
.read(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state ==
|
||||
"Private") {
|
||||
_privateBalanceString =
|
||||
snapshot.data!;
|
||||
} else {
|
||||
_publicBalanceString =
|
||||
snapshot.data!;
|
||||
}
|
||||
}
|
||||
if (ref
|
||||
.read(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state ==
|
||||
"Private" &&
|
||||
_privateBalanceString !=
|
||||
null) {
|
||||
return Text(
|
||||
"$_privateBalanceString ${coin.ticker}",
|
||||
style:
|
||||
STextStyles.itemSubtitle,
|
||||
);
|
||||
} else if (ref
|
||||
.read(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state ==
|
||||
"Public" &&
|
||||
_publicBalanceString !=
|
||||
null) {
|
||||
return Text(
|
||||
"$_publicBalanceString ${coin.ticker}",
|
||||
style:
|
||||
STextStyles.itemSubtitle,
|
||||
);
|
||||
} else {
|
||||
return AnimatedText(
|
||||
stringsToLoopThrough: const [
|
||||
"Loading balance",
|
||||
"Loading balance.",
|
||||
"Loading balance..",
|
||||
"Loading balance...",
|
||||
],
|
||||
style:
|
||||
STextStyles.itemSubtitle,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 8,
|
||||
height: 4,
|
||||
color: CFColors.gray3,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
|
@ -744,10 +997,34 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
BlueTextButton(
|
||||
text: "Send all ${coin.ticker}",
|
||||
onTap: () async {
|
||||
cryptoAmountController.text = (await ref
|
||||
.read(provider)
|
||||
.availableBalance)
|
||||
.toStringAsFixed(Constants.decimalPlaces);
|
||||
if (coin == Coin.firo ||
|
||||
coin == Coin.firoTestNet) {
|
||||
final firoWallet =
|
||||
ref.read(provider).wallet as FiroWallet;
|
||||
if (ref
|
||||
.read(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state ==
|
||||
"Private") {
|
||||
cryptoAmountController.text =
|
||||
(await firoWallet
|
||||
.availablePrivateBalance())
|
||||
.toStringAsFixed(
|
||||
Constants.decimalPlaces);
|
||||
} else {
|
||||
cryptoAmountController.text =
|
||||
(await firoWallet
|
||||
.availablePublicBalance())
|
||||
.toStringAsFixed(
|
||||
Constants.decimalPlaces);
|
||||
}
|
||||
} else {
|
||||
cryptoAmountController.text = (await ref
|
||||
.read(provider)
|
||||
.availableBalance)
|
||||
.toStringAsFixed(Constants.decimalPlaces);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
|
@ -973,74 +1250,126 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
showModalBottomSheet<dynamic>(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
onPressed: (coin == Coin.firo ||
|
||||
coin == Coin.firoTestNet) &&
|
||||
ref
|
||||
.watch(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state ==
|
||||
"Private"
|
||||
? null
|
||||
: () {
|
||||
showModalBottomSheet<dynamic>(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
builder: (_) =>
|
||||
TransactionFeeSelectionSheet(
|
||||
walletId: walletId,
|
||||
amount: Decimal.tryParse(
|
||||
cryptoAmountController
|
||||
.text) ??
|
||||
Decimal.zero,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: ((coin == Coin.firo ||
|
||||
coin == Coin.firoTestNet) &&
|
||||
ref
|
||||
.watch(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state ==
|
||||
"Private")
|
||||
? Row(
|
||||
children: [
|
||||
FutureBuilder(
|
||||
future: _calculateFeesFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
return Text(
|
||||
"~${snapshot.data! as String} ${coin.ticker}",
|
||||
style:
|
||||
STextStyles.itemSubtitle,
|
||||
);
|
||||
} else {
|
||||
return AnimatedText(
|
||||
stringsToLoopThrough: const [
|
||||
"Calculating",
|
||||
"Calculating.",
|
||||
"Calculating..",
|
||||
"Calculating...",
|
||||
],
|
||||
style:
|
||||
STextStyles.itemSubtitle,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
ref
|
||||
.watch(
|
||||
feeRateTypeStateProvider
|
||||
.state)
|
||||
.state
|
||||
.prettyName,
|
||||
style:
|
||||
STextStyles.itemSubtitle12,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _calculateFeesFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState
|
||||
.done &&
|
||||
snapshot.hasData) {
|
||||
return Text(
|
||||
"~${snapshot.data! as String} ${coin.ticker}",
|
||||
style: STextStyles
|
||||
.itemSubtitle,
|
||||
);
|
||||
} else {
|
||||
return AnimatedText(
|
||||
stringsToLoopThrough: const [
|
||||
"Calculating",
|
||||
"Calculating.",
|
||||
"Calculating..",
|
||||
"Calculating...",
|
||||
],
|
||||
style: STextStyles
|
||||
.itemSubtitle,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 8,
|
||||
height: 4,
|
||||
color: CFColors.gray3,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
builder: (_) =>
|
||||
TransactionFeeSelectionSheet(
|
||||
walletId: walletId,
|
||||
amount: Decimal.tryParse(
|
||||
cryptoAmountController.text) ??
|
||||
Decimal.zero,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
ref
|
||||
.watch(feeRateTypeStateProvider
|
||||
.state)
|
||||
.state
|
||||
.prettyName,
|
||||
style: STextStyles.itemSubtitle12,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _calculateFeesFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
return Text(
|
||||
"~${snapshot.data! as String} ${coin.ticker}",
|
||||
style: STextStyles.itemSubtitle,
|
||||
);
|
||||
} else {
|
||||
return AnimatedText(
|
||||
stringsToLoopThrough: const [
|
||||
"Calculating",
|
||||
"Calculating.",
|
||||
"Calculating..",
|
||||
"Calculating...",
|
||||
],
|
||||
style: STextStyles.itemSubtitle,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 8,
|
||||
height: 4,
|
||||
color: CFColors.gray3,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
|
@ -1106,9 +1435,32 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
|
||||
final amount = Format.decimalAmountToSatoshis(
|
||||
_amountToSend!);
|
||||
final availableBalance =
|
||||
Format.decimalAmountToSatoshis(
|
||||
await manager.availableBalance);
|
||||
int availableBalance;
|
||||
if ((coin == Coin.firo ||
|
||||
coin == Coin.firoTestNet)) {
|
||||
if (ref
|
||||
.read(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state ==
|
||||
"Private") {
|
||||
availableBalance =
|
||||
Format.decimalAmountToSatoshis(
|
||||
await (manager.wallet
|
||||
as FiroWallet)
|
||||
.availablePrivateBalance());
|
||||
} else {
|
||||
availableBalance =
|
||||
Format.decimalAmountToSatoshis(
|
||||
await (manager.wallet
|
||||
as FiroWallet)
|
||||
.availablePublicBalance());
|
||||
}
|
||||
} else {
|
||||
availableBalance =
|
||||
Format.decimalAmountToSatoshis(
|
||||
await manager.availableBalance);
|
||||
}
|
||||
|
||||
// confirm send all
|
||||
if (amount == availableBalance) {
|
||||
|
@ -1192,14 +1544,36 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
},
|
||||
));
|
||||
|
||||
final txData = await manager.prepareSend(
|
||||
address: _address!,
|
||||
satoshiAmount: amount,
|
||||
args: {
|
||||
"feeRate":
|
||||
ref.read(feeRateTypeStateProvider)
|
||||
},
|
||||
);
|
||||
Map<String, dynamic> txData;
|
||||
|
||||
if ((coin == Coin.firo ||
|
||||
coin == Coin.firoTestNet) &&
|
||||
ref
|
||||
.read(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state !=
|
||||
"Private") {
|
||||
txData =
|
||||
await (manager.wallet as FiroWallet)
|
||||
.prepareSendPublic(
|
||||
address: _address!,
|
||||
satoshiAmount: amount,
|
||||
args: {
|
||||
"feeRate":
|
||||
ref.read(feeRateTypeStateProvider)
|
||||
},
|
||||
);
|
||||
} else {
|
||||
txData = await manager.prepareSend(
|
||||
address: _address!,
|
||||
satoshiAmount: amount,
|
||||
args: {
|
||||
"feeRate":
|
||||
ref.read(feeRateTypeStateProvider)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (!wasCancelled && mounted) {
|
||||
// pop building dialog
|
||||
|
|
|
@ -0,0 +1,287 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||
import 'package:stackwallet/utilities/cfcolors.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/animated_text.dart';
|
||||
|
||||
class FiroBalanceSelectionSheet extends ConsumerStatefulWidget {
|
||||
const FiroBalanceSelectionSheet({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
}) : super(key: key);
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<FiroBalanceSelectionSheet> createState() =>
|
||||
_FiroBalanceSelectionSheetState();
|
||||
}
|
||||
|
||||
class _FiroBalanceSelectionSheetState
|
||||
extends ConsumerState<FiroBalanceSelectionSheet> {
|
||||
late final String walletId;
|
||||
|
||||
final stringsToLoopThrough = [
|
||||
"Loading balance",
|
||||
"Loading balance.",
|
||||
"Loading balance..",
|
||||
"Loading balance...",
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
walletId = widget.walletId;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
final manager = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId)));
|
||||
final firoWallet = manager.wallet as FiroWallet;
|
||||
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: CFColors.white,
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24,
|
||||
right: 24,
|
||||
top: 10,
|
||||
bottom: 0,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: CFColors.fieldGray,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
width: 60,
|
||||
height: 4,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 36,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Select balance",
|
||||
style: STextStyles.pageTitleH2,
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
final state =
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state;
|
||||
if (state != "Private") {
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
"Private";
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Radio(
|
||||
activeColor: CFColors.link2,
|
||||
value: "Private",
|
||||
groupValue: ref
|
||||
.watch(
|
||||
publicPrivateBalanceStateProvider.state)
|
||||
.state,
|
||||
onChanged: (x) {
|
||||
ref
|
||||
.read(publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state = "Private";
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Row(
|
||||
// children: [
|
||||
Text(
|
||||
"Private balance",
|
||||
style: STextStyles.titleBold12.copyWith(
|
||||
color: const Color(0xFF44464E),
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 2,
|
||||
),
|
||||
FutureBuilder(
|
||||
future: firoWallet.availablePrivateBalance(),
|
||||
builder:
|
||||
(context, AsyncSnapshot<Decimal> snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
return Text(
|
||||
"${snapshot.data!} ${manager.coin.ticker}",
|
||||
style: STextStyles.itemSubtitle,
|
||||
textAlign: TextAlign.left,
|
||||
);
|
||||
} else {
|
||||
return AnimatedText(
|
||||
stringsToLoopThrough:
|
||||
stringsToLoopThrough,
|
||||
style: STextStyles.itemSubtitle,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
// ],
|
||||
// ),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
final state =
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state;
|
||||
if (state != "Public") {
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
"Public";
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Radio(
|
||||
activeColor: CFColors.link2,
|
||||
value: "Public",
|
||||
groupValue: ref
|
||||
.watch(
|
||||
publicPrivateBalanceStateProvider.state)
|
||||
.state,
|
||||
onChanged: (x) {
|
||||
ref
|
||||
.read(publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state = "Public";
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Row(
|
||||
// children: [
|
||||
Text(
|
||||
"Public balance",
|
||||
style: STextStyles.titleBold12.copyWith(
|
||||
color: const Color(0xFF44464E),
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 2,
|
||||
),
|
||||
FutureBuilder(
|
||||
future: firoWallet.availablePublicBalance(),
|
||||
builder:
|
||||
(context, AsyncSnapshot<Decimal> snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
return Text(
|
||||
"${snapshot.data!} ${manager.coin.ticker}",
|
||||
style: STextStyles.itemSubtitle,
|
||||
textAlign: TextAlign.left,
|
||||
);
|
||||
} else {
|
||||
return AnimatedText(
|
||||
stringsToLoopThrough:
|
||||
stringsToLoopThrough,
|
||||
style: STextStyles.itemSubtitle,
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
// ],
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||
import 'package:stackwallet/utilities/cfcolors.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
@ -58,35 +60,63 @@ class _TransactionFeeSelectionSheetState
|
|||
required int amount,
|
||||
required FeeRateType feeRateType,
|
||||
required int feeRate,
|
||||
required Coin coin,
|
||||
}) async {
|
||||
switch (feeRateType) {
|
||||
case FeeRateType.fast:
|
||||
if (ref.read(feeSheetSessionCacheProvider).fast[amount] == null) {
|
||||
ref.read(feeSheetSessionCacheProvider).fast[amount] =
|
||||
Format.satoshisToAmount(await ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(walletId)
|
||||
.estimateFeeFor(amount, feeRate));
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||
"Private") {
|
||||
ref.read(feeSheetSessionCacheProvider).fast[amount] =
|
||||
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
|
||||
.estimateFeeForPublic(amount, feeRate));
|
||||
} else {
|
||||
ref.read(feeSheetSessionCacheProvider).fast[amount] =
|
||||
Format.satoshisToAmount(
|
||||
await manager.estimateFeeFor(amount, feeRate));
|
||||
}
|
||||
}
|
||||
return ref.read(feeSheetSessionCacheProvider).fast[amount]!;
|
||||
|
||||
case FeeRateType.average:
|
||||
if (ref.read(feeSheetSessionCacheProvider).average[amount] == null) {
|
||||
ref.read(feeSheetSessionCacheProvider).average[amount] =
|
||||
Format.satoshisToAmount(await ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(walletId)
|
||||
.estimateFeeFor(amount, feeRate));
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||
"Private") {
|
||||
ref.read(feeSheetSessionCacheProvider).average[amount] =
|
||||
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
|
||||
.estimateFeeForPublic(amount, feeRate));
|
||||
} else {
|
||||
ref.read(feeSheetSessionCacheProvider).average[amount] =
|
||||
Format.satoshisToAmount(
|
||||
await manager.estimateFeeFor(amount, feeRate));
|
||||
}
|
||||
}
|
||||
return ref.read(feeSheetSessionCacheProvider).average[amount]!;
|
||||
|
||||
case FeeRateType.slow:
|
||||
if (ref.read(feeSheetSessionCacheProvider).slow[amount] == null) {
|
||||
ref.read(feeSheetSessionCacheProvider).slow[amount] =
|
||||
Format.satoshisToAmount(await ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(walletId)
|
||||
.estimateFeeFor(amount, feeRate));
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(walletId);
|
||||
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state !=
|
||||
"Private") {
|
||||
ref.read(feeSheetSessionCacheProvider).slow[amount] =
|
||||
Format.satoshisToAmount(await (manager.wallet as FiroWallet)
|
||||
.estimateFeeForPublic(amount, feeRate));
|
||||
} else {
|
||||
ref.read(feeSheetSessionCacheProvider).slow[amount] =
|
||||
Format.satoshisToAmount(
|
||||
await manager.estimateFeeFor(amount, feeRate));
|
||||
}
|
||||
}
|
||||
return ref.read(feeSheetSessionCacheProvider).slow[amount]!;
|
||||
}
|
||||
|
@ -249,6 +279,7 @@ class _TransactionFeeSelectionSheetState
|
|||
if (feeObject != null)
|
||||
FutureBuilder(
|
||||
future: feeFor(
|
||||
coin: manager.coin,
|
||||
feeRateType: FeeRateType.fast,
|
||||
feeRate: feeObject!.fast,
|
||||
amount: Format
|
||||
|
@ -372,6 +403,7 @@ class _TransactionFeeSelectionSheetState
|
|||
if (feeObject != null)
|
||||
FutureBuilder(
|
||||
future: feeFor(
|
||||
coin: manager.coin,
|
||||
feeRateType: FeeRateType.fast,
|
||||
feeRate: feeObject!.fast,
|
||||
amount: Format
|
||||
|
@ -496,6 +528,7 @@ class _TransactionFeeSelectionSheetState
|
|||
if (feeObject != null)
|
||||
FutureBuilder(
|
||||
future: feeFor(
|
||||
coin: manager.coin,
|
||||
feeRateType: FeeRateType.slow,
|
||||
feeRate: feeObject!.slow,
|
||||
amount: Format
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/providers/global/debug_service_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/cfcolors.dart';
|
||||
import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
|
||||
class HiddenSettings extends StatelessWidget {
|
||||
const HiddenSettings({Key? key}) : super(key: key);
|
||||
|
@ -55,11 +54,11 @@ class HiddenSettings extends StatelessWidget {
|
|||
.read(notificationsProvider)
|
||||
.delete(notifs[0], true);
|
||||
|
||||
showFloatingFlushBar(
|
||||
unawaited(showFloatingFlushBar(
|
||||
type: FlushBarType.success,
|
||||
message: "Notification history deleted",
|
||||
context: context,
|
||||
);
|
||||
));
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
child: Text(
|
||||
|
@ -109,11 +108,11 @@ class HiddenSettings extends StatelessWidget {
|
|||
.read(debugServiceProvider)
|
||||
.deleteAllMessages();
|
||||
|
||||
showFloatingFlushBar(
|
||||
unawaited(showFloatingFlushBar(
|
||||
type: FlushBarType.success,
|
||||
message: "Debug Logs deleted",
|
||||
context: context,
|
||||
);
|
||||
));
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
child: Text(
|
||||
|
@ -125,34 +124,34 @@ class HiddenSettings extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return StackDialogBase(
|
||||
child: SizedBox(
|
||||
width: 200,
|
||||
child: Lottie.asset(
|
||||
Assets.lottie.test2,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"Lottie test",
|
||||
style: STextStyles.button.copyWith(
|
||||
color: CFColors.stackAccent,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// const SizedBox(
|
||||
// height: 12,
|
||||
// ),
|
||||
// GestureDetector(
|
||||
// onTap: () async {
|
||||
// showDialog<void>(
|
||||
// context: context,
|
||||
// builder: (_) {
|
||||
// return StackDialogBase(
|
||||
// child: SizedBox(
|
||||
// width: 200,
|
||||
// child: Lottie.asset(
|
||||
// Assets.lottie.test2,
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// },
|
||||
// child: RoundedWhiteContainer(
|
||||
// child: Text(
|
||||
// "Lottie test",
|
||||
// style: STextStyles.button.copyWith(
|
||||
// color: CFColors.stackAccent,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/models/paymint/transactions_model.dart';
|
||||
|
@ -110,7 +112,7 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
.read(walletsChangeNotifierProvider)
|
||||
.getManagerProvider(widget.walletId);
|
||||
if (!ref.read(managerProvider).isRefreshing) {
|
||||
ref.read(managerProvider).refresh();
|
||||
unawaited(ref.read(managerProvider).refresh());
|
||||
}
|
||||
},
|
||||
child: ListView.builder(
|
||||
|
|
|
@ -10,6 +10,16 @@ class TxIcon extends StatelessWidget {
|
|||
static const Size size = Size(32, 32);
|
||||
|
||||
String _getAssetName(bool isCancelled, bool isReceived, bool isPending) {
|
||||
if (!isReceived && transaction.subType == "mint") {
|
||||
if (isCancelled) {
|
||||
return Assets.svg.anonymizeFailed;
|
||||
}
|
||||
if (isPending) {
|
||||
return Assets.svg.anonymizePending;
|
||||
}
|
||||
return Assets.svg.anonymize;
|
||||
}
|
||||
|
||||
if (isReceived) {
|
||||
if (isCancelled) {
|
||||
return Assets.svg.receiveCancelled;
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart';
|
||||
import 'package:stackwallet/utilities/cfcolors.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
|
||||
|
@ -18,6 +20,9 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
|
|||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final maxHeight = MediaQuery.of(context).size.height * 0.60;
|
||||
|
||||
final coin = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).coin));
|
||||
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: CFColors.white,
|
||||
|
@ -105,25 +110,44 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
|
|||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Available balance",
|
||||
style: STextStyles.titleBold12,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
// TODO need text from design
|
||||
Text(
|
||||
"Current spendable (unlocked) balance",
|
||||
style: STextStyles.itemSubtitle12.copyWith(
|
||||
color: CFColors.neutral60,
|
||||
if (coin != Coin.firo && coin != Coin.firoTestNet)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Available balance",
|
||||
style: STextStyles.titleBold12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Text(
|
||||
"Current spendable (unlocked) balance",
|
||||
style: STextStyles.itemSubtitle12.copyWith(
|
||||
color: CFColors.neutral60,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Private balance",
|
||||
style: STextStyles.titleBold12,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Text(
|
||||
"Current private spendable (unlocked) balance",
|
||||
style: STextStyles.itemSubtitle12.copyWith(
|
||||
color: CFColors.neutral60,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -172,25 +196,44 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
|
|||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Full balance",
|
||||
style: STextStyles.titleBold12,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
// TODO need text from design
|
||||
Text(
|
||||
"Total wallet balance",
|
||||
style: STextStyles.itemSubtitle12.copyWith(
|
||||
color: CFColors.neutral60,
|
||||
if (coin != Coin.firo && coin != Coin.firoTestNet)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Full balance",
|
||||
style: STextStyles.titleBold12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Text(
|
||||
"Total wallet balance",
|
||||
style: STextStyles.itemSubtitle12.copyWith(
|
||||
color: CFColors.neutral60,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Public balance",
|
||||
style: STextStyles.titleBold12,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Text(
|
||||
"Current public spendable (unlocked) balance",
|
||||
style: STextStyles.itemSubtitle12.copyWith(
|
||||
color: CFColors.neutral60,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_balance_toggle_
|
|||
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_refresh_button.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart';
|
||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||
import 'package:stackwallet/services/coins/manager.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
|
@ -67,15 +68,25 @@ class _WalletSummaryInfoState extends State<WalletSummaryInfo> {
|
|||
Expanded(
|
||||
child: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
final totalBalanceFuture = ref
|
||||
.watch(managerProvider.select((value) => value.totalBalance));
|
||||
|
||||
final availableBalanceFuture = ref.watch(
|
||||
managerProvider.select((value) => value.availableBalance));
|
||||
|
||||
final Coin coin =
|
||||
ref.watch(managerProvider.select((value) => value.coin));
|
||||
|
||||
Future<Decimal>? totalBalanceFuture;
|
||||
Future<Decimal>? availableBalanceFuture;
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
final firoWallet =
|
||||
ref.watch(managerProvider.select((value) => value.wallet))
|
||||
as FiroWallet;
|
||||
totalBalanceFuture = firoWallet.availablePublicBalance();
|
||||
availableBalanceFuture = firoWallet.availablePrivateBalance();
|
||||
} else {
|
||||
totalBalanceFuture = ref.watch(
|
||||
managerProvider.select((value) => value.totalBalance));
|
||||
|
||||
availableBalanceFuture = ref.watch(
|
||||
managerProvider.select((value) => value.availableBalance));
|
||||
}
|
||||
|
||||
final locale = ref.watch(localeServiceChangeNotifierProvider
|
||||
.select((value) => value.locale));
|
||||
|
||||
|
@ -114,12 +125,20 @@ class _WalletSummaryInfoState extends State<WalletSummaryInfo> {
|
|||
onTap: showSheet,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"${_showAvailable ? "Available" : "Full"} Balance",
|
||||
style: STextStyles.subtitle.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet)
|
||||
Text(
|
||||
"${_showAvailable ? "Private" : "Public"} Balance",
|
||||
style: STextStyles.subtitle.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
if (coin != Coin.firo && coin != Coin.firoTestNet)
|
||||
Text(
|
||||
"${_showAvailable ? "Available" : "Full"} Balance",
|
||||
style: STextStyles.subtitle.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
|
@ -166,12 +185,20 @@ class _WalletSummaryInfoState extends State<WalletSummaryInfo> {
|
|||
onTap: showSheet,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"${_showAvailable ? "Available" : "Full"} Balance",
|
||||
style: STextStyles.subtitle.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet)
|
||||
Text(
|
||||
"${_showAvailable ? "Private" : "Public"} Balance",
|
||||
style: STextStyles.subtitle.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
if (coin != Coin.firo && coin != Coin.firoTestNet)
|
||||
Text(
|
||||
"${_showAvailable ? "Available" : "Full"} Balance",
|
||||
style: STextStyles.subtitle.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
@ -67,13 +69,19 @@ class _TransactionDetailsViewState
|
|||
coin = widget.coin;
|
||||
amount = Format.satoshisToAmount(_transaction.amount);
|
||||
fee = Format.satoshisToAmount(_transaction.fees);
|
||||
amountPrefix = _transaction.txType.toLowerCase() == "sent" ? "- " : "+ ";
|
||||
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
showFeePending = true;
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
_transaction.subType == "mint") {
|
||||
amountPrefix = "";
|
||||
} else {
|
||||
showFeePending = false;
|
||||
amountPrefix = _transaction.txType.toLowerCase() == "sent" ? "- " : "+ ";
|
||||
}
|
||||
|
||||
// if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
// showFeePending = true;
|
||||
// } else {
|
||||
// showFeePending = false;
|
||||
// }
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
@ -83,10 +91,21 @@ class _TransactionDetailsViewState
|
|||
}
|
||||
|
||||
String whatIsIt(String type) {
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
if (_transaction.subType == "mint") {
|
||||
if (_transaction.confirmedStatus) {
|
||||
return "Minted";
|
||||
} else {
|
||||
return "Minting";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type == "Received") {
|
||||
if (_transaction.isMinting) {
|
||||
return "Minting";
|
||||
} else if (_transaction.confirmedStatus) {
|
||||
// if (_transaction.isMinting) {
|
||||
// return "Minting";
|
||||
// } else
|
||||
if (_transaction.confirmedStatus) {
|
||||
return "Received";
|
||||
} else {
|
||||
return "Receiving";
|
||||
|
@ -124,6 +143,66 @@ class _TransactionDetailsViewState
|
|||
|
||||
String _note = "";
|
||||
|
||||
Future<bool> showExplorerWarning(String explorer) async {
|
||||
final bool? shouldContinue = await showDialog<bool>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => StackDialog(
|
||||
title: "Attention",
|
||||
message:
|
||||
"You are about to view this transaction in a block explorer. The explorer may log your IP address and link it to the transaction. Only proceed if you trust $explorer.",
|
||||
icon: Row(
|
||||
children: [
|
||||
Consumer(builder: (_, ref, __) {
|
||||
return Checkbox(
|
||||
value: ref.watch(prefsChangeNotifierProvider
|
||||
.select((value) => value.hideBlockExplorerWarning)),
|
||||
onChanged: (value) {
|
||||
if (value is bool) {
|
||||
ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.hideBlockExplorerWarning = value;
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
}),
|
||||
Text(
|
||||
"Never show again",
|
||||
style: STextStyles.smallMed14,
|
||||
)
|
||||
],
|
||||
),
|
||||
leftButton: TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: STextStyles.button.copyWith(
|
||||
color: CFColors.stackAccent,
|
||||
),
|
||||
),
|
||||
),
|
||||
rightButton: TextButton(
|
||||
style: Theme.of(context).textButtonTheme.style?.copyWith(
|
||||
backgroundColor: MaterialStateProperty.all<Color>(
|
||||
CFColors.stackAccent,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: Text(
|
||||
"Continue",
|
||||
style: STextStyles.button,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return shouldContinue ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -221,12 +300,16 @@ class _TransactionDetailsViewState
|
|||
),
|
||||
),
|
||||
if (!(coin == Coin.monero &&
|
||||
_transaction.txType.toLowerCase() == "sent"))
|
||||
_transaction.txType.toLowerCase() == "sent") &&
|
||||
!((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
_transaction.subType == "mint"))
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
if (!(coin == Coin.monero &&
|
||||
_transaction.txType.toLowerCase() == "sent"))
|
||||
_transaction.txType.toLowerCase() == "sent") &&
|
||||
!((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
_transaction.subType == "mint"))
|
||||
RoundedWhiteContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -469,6 +552,19 @@ class _TransactionDetailsViewState
|
|||
coin: coin,
|
||||
txid: _transaction.txid,
|
||||
);
|
||||
|
||||
if (ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.hideBlockExplorerWarning ==
|
||||
false) {
|
||||
final shouldContinue =
|
||||
await showExplorerWarning(uri.host);
|
||||
|
||||
if (!shouldContinue) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ref
|
||||
// .read(
|
||||
// shouldShowLockscreenOnResumeStateProvider
|
||||
|
@ -480,14 +576,14 @@ class _TransactionDetailsViewState
|
|||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
} catch (_) {
|
||||
showDialog<void>(
|
||||
unawaited(showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "Could not open in block explorer",
|
||||
message:
|
||||
"Failed to open \"${uri.toString()}\"",
|
||||
),
|
||||
);
|
||||
));
|
||||
} finally {
|
||||
// Future<void>.delayed(
|
||||
// const Duration(seconds: 1),
|
||||
|
@ -505,83 +601,83 @@ class _TransactionDetailsViewState
|
|||
],
|
||||
),
|
||||
),
|
||||
if ((coin == Coin.firoTestNet || coin == Coin.firo) &&
|
||||
_transaction.subType == "mint")
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
if ((coin == Coin.firoTestNet || coin == Coin.firo) &&
|
||||
_transaction.subType == "mint")
|
||||
RoundedWhiteContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Mint Transaction ID",
|
||||
style: STextStyles.itemSubtitle,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
// Flexible(
|
||||
// child: FittedBox(
|
||||
// fit: BoxFit.scaleDown,
|
||||
// child:
|
||||
SelectableText(
|
||||
_transaction.otherData ?? "Unknown",
|
||||
style: STextStyles.itemSubtitle12,
|
||||
),
|
||||
// ),
|
||||
// ),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
BlueTextButton(
|
||||
text: "Open in block explorer",
|
||||
onTap: () async {
|
||||
final uri = getBlockExplorerTransactionUrlFor(
|
||||
coin: coin,
|
||||
txid: _transaction.otherData ?? "Unknown",
|
||||
);
|
||||
// ref
|
||||
// .read(
|
||||
// shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = false;
|
||||
try {
|
||||
await launchUrl(
|
||||
uri,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
} catch (_) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "Could not open in block explorer",
|
||||
message:
|
||||
"Failed to open \"${uri.toString()}\"",
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
// Future<void>.delayed(
|
||||
// const Duration(seconds: 1),
|
||||
// () => ref
|
||||
// .read(
|
||||
// shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = true,
|
||||
// );
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// if ((coin == Coin.firoTestNet || coin == Coin.firo) &&
|
||||
// _transaction.subType == "mint")
|
||||
// const SizedBox(
|
||||
// height: 12,
|
||||
// ),
|
||||
// if ((coin == Coin.firoTestNet || coin == Coin.firo) &&
|
||||
// _transaction.subType == "mint")
|
||||
// RoundedWhiteContainer(
|
||||
// child: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Text(
|
||||
// "Mint Transaction ID",
|
||||
// style: STextStyles.itemSubtitle,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// height: 8,
|
||||
// ),
|
||||
// // Flexible(
|
||||
// // child: FittedBox(
|
||||
// // fit: BoxFit.scaleDown,
|
||||
// // child:
|
||||
// SelectableText(
|
||||
// _transaction.otherData ?? "Unknown",
|
||||
// style: STextStyles.itemSubtitle12,
|
||||
// ),
|
||||
// // ),
|
||||
// // ),
|
||||
// const SizedBox(
|
||||
// height: 8,
|
||||
// ),
|
||||
// BlueTextButton(
|
||||
// text: "Open in block explorer",
|
||||
// onTap: () async {
|
||||
// final uri = getBlockExplorerTransactionUrlFor(
|
||||
// coin: coin,
|
||||
// txid: _transaction.otherData ?? "Unknown",
|
||||
// );
|
||||
// // ref
|
||||
// // .read(
|
||||
// // shouldShowLockscreenOnResumeStateProvider
|
||||
// // .state)
|
||||
// // .state = false;
|
||||
// try {
|
||||
// await launchUrl(
|
||||
// uri,
|
||||
// mode: LaunchMode.externalApplication,
|
||||
// );
|
||||
// } catch (_) {
|
||||
// unawaited(showDialog<void>(
|
||||
// context: context,
|
||||
// builder: (_) => StackOkDialog(
|
||||
// title: "Could not open in block explorer",
|
||||
// message:
|
||||
// "Failed to open \"${uri.toString()}\"",
|
||||
// ),
|
||||
// ));
|
||||
// } finally {
|
||||
// // Future<void>.delayed(
|
||||
// // const Duration(seconds: 1),
|
||||
// // () => ref
|
||||
// // .read(
|
||||
// // shouldShowLockscreenOnResumeStateProvider
|
||||
// // .state)
|
||||
// // .state = true,
|
||||
// // );
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
if (coin == Coin.epicCash)
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
|
@ -637,20 +733,20 @@ class _TransactionDetailsViewState
|
|||
if (manager.wallet is EpicCashWallet) {
|
||||
final String? id = _transaction.slateId;
|
||||
if (id == null) {
|
||||
showFloatingFlushBar(
|
||||
unawaited(showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "Could not find Epic transaction ID",
|
||||
context: context,
|
||||
);
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
showDialog<dynamic>(
|
||||
unawaited(showDialog<dynamic>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (_) =>
|
||||
const CancellingTransactionProgressDialog(),
|
||||
);
|
||||
));
|
||||
|
||||
final result = await (manager.wallet as EpicCashWallet)
|
||||
.cancelPendingTransactionAndPost(id);
|
||||
|
@ -681,11 +777,11 @@ class _TransactionDetailsViewState
|
|||
}
|
||||
}
|
||||
} else {
|
||||
showFloatingFlushBar(
|
||||
unawaited(showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "ERROR: Wallet type is not Epic Cash",
|
||||
context: context,
|
||||
);
|
||||
));
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:event_bus/event_bus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart';
|
||||
import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart';
|
||||
import 'package:stackwallet/pages/home_view/home_view.dart';
|
||||
|
@ -22,6 +24,7 @@ import 'package:stackwallet/providers/global/auto_swb_service_provider.dart';
|
|||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
||||
import 'package:stackwallet/providers/ui/unread_notifications_provider.dart';
|
||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||
import 'package:stackwallet/services/coins/manager.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
|
@ -31,12 +34,18 @@ import 'package:stackwallet/utilities/cfcolors.dart';
|
|||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/flush_bar_type.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.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/stack_dialog.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../providers/wallet/public_private_balance_state_provider.dart';
|
||||
import '../../providers/wallet/wallet_balance_toggle_state_provider.dart';
|
||||
import '../../utilities/enums/wallet_balance_toggle_state.dart';
|
||||
|
||||
/// [eventBus] should only be set during testing
|
||||
class WalletView extends ConsumerStatefulWidget {
|
||||
const WalletView({
|
||||
|
@ -155,7 +164,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
const timeout = Duration(milliseconds: 1500);
|
||||
if (_cachedTime == null || now.difference(_cachedTime!) > timeout) {
|
||||
_cachedTime = now;
|
||||
showDialog<dynamic>(
|
||||
unawaited(showDialog<dynamic>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => WillPopScope(
|
||||
|
@ -173,7 +182,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
onTimeout: () => Navigator.of(context).popUntil(
|
||||
ModalRoute.withName(WalletView.routeName),
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -222,14 +231,14 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
final coin = ref.read(managerProvider).coin;
|
||||
|
||||
if (coin == Coin.epicCash) {
|
||||
showDialog<void>(
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => const StackOkDialog(
|
||||
title: "ChangeNOW not available for Epic Cash",
|
||||
),
|
||||
);
|
||||
} else if (coin.name.endsWith("TestNet")) {
|
||||
showDialog<void>(
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => const StackOkDialog(
|
||||
title: "ChangeNOW not available for test net coins",
|
||||
|
@ -247,10 +256,10 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
element.ticker.toLowerCase() == coin.ticker.toLowerCase());
|
||||
|
||||
if (currencies.isNotEmpty) {
|
||||
ref
|
||||
unawaited(ref
|
||||
.read(estimatedRateExchangeFormProvider)
|
||||
.updateFrom(currencies.first, false);
|
||||
ref.read(estimatedRateExchangeFormProvider).updateTo(
|
||||
.updateFrom(currencies.first, false));
|
||||
unawaited(ref.read(estimatedRateExchangeFormProvider).updateTo(
|
||||
ref
|
||||
.read(availableChangeNowCurrenciesStateProvider.state)
|
||||
.state
|
||||
|
@ -258,16 +267,82 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
(element) =>
|
||||
element.ticker.toLowerCase() != coin.ticker.toLowerCase(),
|
||||
),
|
||||
false);
|
||||
false));
|
||||
}
|
||||
|
||||
Navigator.of(context).pushNamed(
|
||||
unawaited(Navigator.of(context).pushNamed(
|
||||
WalletInitiatedExchangeView.routeName,
|
||||
arguments: Tuple2(
|
||||
walletId,
|
||||
coin,
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> attemptAnonymize() async {
|
||||
bool shouldPop = false;
|
||||
unawaited(
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => WillPopScope(
|
||||
child: const CustomLoadingOverlay(
|
||||
message: "Anonymizing balance",
|
||||
eventBus: null,
|
||||
),
|
||||
onWillPop: () async => shouldPop,
|
||||
),
|
||||
),
|
||||
);
|
||||
final firoWallet = ref.read(managerProvider).wallet as FiroWallet;
|
||||
|
||||
final publicBalance = await firoWallet.availablePublicBalance();
|
||||
if (publicBalance <= Decimal.zero) {
|
||||
shouldPop = true;
|
||||
if (mounted) {
|
||||
Navigator.of(context).popUntil(
|
||||
ModalRoute.withName(WalletView.routeName),
|
||||
);
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "No funds available to anonymize!",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await firoWallet.anonymizeAllPublicFunds();
|
||||
shouldPop = true;
|
||||
if (mounted) {
|
||||
Navigator.of(context).popUntil(
|
||||
ModalRoute.withName(WalletView.routeName),
|
||||
);
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.success,
|
||||
message: "Anonymize transaction submitted",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
shouldPop = true;
|
||||
if (mounted) {
|
||||
Navigator.of(context).popUntil(
|
||||
ModalRoute.withName(WalletView.routeName),
|
||||
);
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: "Anonymize all failed",
|
||||
message: "Reason: $e",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -275,6 +350,8 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
final coin = ref.watch(managerProvider.select((value) => value.coin));
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: _onWillPop,
|
||||
child: Scaffold(
|
||||
|
@ -283,9 +360,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
title: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.iconFor(
|
||||
coin: ref
|
||||
.watch(managerProvider.select((value) => value.coin))),
|
||||
Assets.svg.iconFor(coin: coin),
|
||||
// color: CFColors.stackAccent,
|
||||
width: 24,
|
||||
height: 24,
|
||||
|
@ -440,6 +515,69 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (coin == Coin.firo)
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
if (coin == Coin.firo)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: () async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => StackDialog(
|
||||
title: "Attention!",
|
||||
message:
|
||||
"You're about to anonymize all of your public funds.",
|
||||
leftButton: TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: STextStyles.button.copyWith(
|
||||
color: CFColors.stackAccent,
|
||||
),
|
||||
),
|
||||
),
|
||||
rightButton: TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
unawaited(attemptAnonymize());
|
||||
},
|
||||
style: Theme.of(context)
|
||||
.textButtonTheme
|
||||
.style
|
||||
?.copyWith(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.all<Color>(
|
||||
CFColors.stackAccent,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
"Continue",
|
||||
style: STextStyles.button,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
"Anonymize funds",
|
||||
style: STextStyles.button.copyWith(
|
||||
color: CFColors.stackAccent,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
@ -532,19 +670,17 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
onExchangePressed: () =>
|
||||
_onExchangePressed(context),
|
||||
onReceivePressed: () async {
|
||||
final address = await ref
|
||||
.read(managerProvider)
|
||||
.currentReceivingAddress;
|
||||
final coin =
|
||||
ref.read(managerProvider).coin;
|
||||
if (mounted) {
|
||||
Navigator.of(context).pushNamed(
|
||||
unawaited(
|
||||
Navigator.of(context).pushNamed(
|
||||
ReceiveView.routeName,
|
||||
arguments: Tuple2(
|
||||
address,
|
||||
walletId,
|
||||
coin,
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
},
|
||||
onSendPressed: () {
|
||||
|
@ -552,6 +688,25 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
ref.read(managerProvider).walletId;
|
||||
final coin =
|
||||
ref.read(managerProvider).coin;
|
||||
switch (ref
|
||||
.read(walletBalanceToggleStateProvider
|
||||
.state)
|
||||
.state) {
|
||||
case WalletBalanceToggleState.full:
|
||||
ref
|
||||
.read(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state = "Public";
|
||||
break;
|
||||
case WalletBalanceToggleState.available:
|
||||
ref
|
||||
.read(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state = "Private";
|
||||
break;
|
||||
}
|
||||
Navigator.of(context).pushNamed(
|
||||
SendView.routeName,
|
||||
arguments: Tuple2(
|
||||
|
|
|
@ -2,4 +2,4 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:stackwallet/models/exchange/change_now/currency.dart';
|
||||
|
||||
final availableChangeNowCurrenciesStateProvider =
|
||||
StateProvider<List<Currency>>((ref) => []);
|
||||
StateProvider<List<Currency>>((ref) => <Currency>[]);
|
||||
|
|
|
@ -2,4 +2,5 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart';
|
||||
|
||||
final availableFloatingRatePairsStateProvider =
|
||||
StateProvider<List<AvailableFloatingRatePair>>((ref) => []);
|
||||
StateProvider<List<AvailableFloatingRatePair>>(
|
||||
(ref) => <AvailableFloatingRatePair>[]);
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final publicPrivateBalanceStateProvider =
|
||||
StateProvider<String>((_) => "Private");
|
|
@ -681,7 +681,7 @@ class RouteGenerator {
|
|||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => ReceiveView(
|
||||
receivingAddress: args.item1,
|
||||
walletId: args.item1,
|
||||
coin: args.item2,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:http/http.dart' as http;
|
|||
import 'package:stackwallet/external_api_keys.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/change_now_response.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/currency.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart';
|
||||
|
@ -17,6 +18,7 @@ class ChangeNow {
|
|||
static const String scheme = "https";
|
||||
static const String authority = "api.changenow.io";
|
||||
static const String apiVersion = "/v1";
|
||||
static const String apiVersionV2 = "/v2";
|
||||
|
||||
ChangeNow._();
|
||||
static final ChangeNow _instance = ChangeNow._();
|
||||
|
@ -29,6 +31,10 @@ class ChangeNow {
|
|||
return Uri.https(authority, apiVersion + path, params);
|
||||
}
|
||||
|
||||
Uri _buildUriV2(String path, Map<String, dynamic>? params) {
|
||||
return Uri.https(authority, apiVersionV2 + path, params);
|
||||
}
|
||||
|
||||
Future<dynamic> _makeGetRequest(Uri uri) async {
|
||||
final client = this.client ?? http.Client();
|
||||
try {
|
||||
|
@ -47,6 +53,27 @@ class ChangeNow {
|
|||
}
|
||||
}
|
||||
|
||||
Future<dynamic> _makeGetRequestV2(Uri uri, String apiKey) async {
|
||||
final client = this.client ?? http.Client();
|
||||
try {
|
||||
final response = await client.get(
|
||||
uri,
|
||||
headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
'x-changenow-api-key': apiKey,
|
||||
},
|
||||
);
|
||||
|
||||
final parsed = jsonDecode(response.body);
|
||||
|
||||
return parsed;
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("_makeRequestV2($uri) threw: $e\n$s", level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> _makePostRequest(
|
||||
Uri uri,
|
||||
Map<String, String> body,
|
||||
|
@ -283,37 +310,109 @@ class ChangeNow {
|
|||
}
|
||||
}
|
||||
|
||||
// old v1 version
|
||||
/// This API endpoint returns fixed-rate estimated exchange amount of
|
||||
/// [toTicker] cryptocurrency to receive for [fromAmount] of [fromTicker]
|
||||
Future<ChangeNowResponse<EstimatedExchangeAmount>>
|
||||
getEstimatedFixedRateExchangeAmount({
|
||||
// Future<ChangeNowResponse<EstimatedExchangeAmount>>
|
||||
// getEstimatedFixedRateExchangeAmount({
|
||||
// required String fromTicker,
|
||||
// required String toTicker,
|
||||
// required Decimal fromAmount,
|
||||
// // (Optional) Use rateId for fixed-rate flow. If this field is true, you
|
||||
// // could use returned field "rateId" in next method for creating transaction
|
||||
// // to freeze estimated amount that you got in this method. Current estimated
|
||||
// // amount would be valid until time in field "validUntil"
|
||||
// bool useRateId = true,
|
||||
// String? apiKey,
|
||||
// }) async {
|
||||
// Map<String, dynamic> params = {
|
||||
// "api_key": apiKey ?? kChangeNowApiKey,
|
||||
// "useRateId": useRateId.toString(),
|
||||
// };
|
||||
//
|
||||
// final uri = _buildUri(
|
||||
// "/exchange-amount/fixed-rate/${fromAmount.toString()}/${fromTicker}_$toTicker",
|
||||
// params,
|
||||
// );
|
||||
//
|
||||
// try {
|
||||
// // simple json object is expected here
|
||||
// final json = await _makeGetRequest(uri);
|
||||
//
|
||||
// try {
|
||||
// final value = EstimatedExchangeAmount.fromJson(
|
||||
// Map<String, dynamic>.from(json as Map));
|
||||
// return ChangeNowResponse(value: value);
|
||||
// } catch (_) {
|
||||
// return ChangeNowResponse(
|
||||
// exception: ChangeNowException(
|
||||
// "Failed to serialize $json",
|
||||
// ChangeNowExceptionType.serializeResponseError,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// } catch (e, s) {
|
||||
// Logging.instance.log(
|
||||
// "getEstimatedFixedRateExchangeAmount exception: $e\n$s",
|
||||
// level: LogLevel.Error);
|
||||
// return ChangeNowResponse(
|
||||
// exception: ChangeNowException(
|
||||
// e.toString(),
|
||||
// ChangeNowExceptionType.generic,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Get estimated amount of [toTicker] cryptocurrency to receive
|
||||
/// for [fromAmount] of [fromTicker]
|
||||
Future<ChangeNowResponse<CNExchangeEstimate>> getEstimatedExchangeAmountV2({
|
||||
required String fromTicker,
|
||||
required String toTicker,
|
||||
required Decimal fromAmount,
|
||||
// (Optional) Use rateId for fixed-rate flow. If this field is true, you
|
||||
// could use returned field "rateId" in next method for creating transaction
|
||||
// to freeze estimated amount that you got in this method. Current estimated
|
||||
// amount would be valid until time in field "validUntil"
|
||||
bool useRateId = true,
|
||||
required CNEstimateType fromOrTo,
|
||||
required Decimal amount,
|
||||
String? fromNetwork,
|
||||
String? toNetwork,
|
||||
CNFlowType flow = CNFlowType.standard,
|
||||
String? apiKey,
|
||||
}) async {
|
||||
Map<String, dynamic> params = {
|
||||
"api_key": apiKey ?? kChangeNowApiKey,
|
||||
"useRateId": useRateId.toString(),
|
||||
Map<String, dynamic>? params = {
|
||||
"fromCurrency": fromTicker,
|
||||
"toCurrency": toTicker,
|
||||
"flow": flow.value,
|
||||
"type": fromOrTo.name,
|
||||
};
|
||||
|
||||
final uri = _buildUri(
|
||||
"/exchange-amount/fixed-rate/${fromAmount.toString()}/${fromTicker}_$toTicker",
|
||||
params,
|
||||
);
|
||||
switch (fromOrTo) {
|
||||
case CNEstimateType.direct:
|
||||
params["fromAmount"] = amount.toString();
|
||||
break;
|
||||
case CNEstimateType.reverse:
|
||||
params["toAmount"] = amount.toString();
|
||||
break;
|
||||
}
|
||||
|
||||
if (fromNetwork != null) {
|
||||
params["fromNetwork"] = fromNetwork;
|
||||
}
|
||||
|
||||
if (toNetwork != null) {
|
||||
params["toNetwork"] = toNetwork;
|
||||
}
|
||||
|
||||
if (flow == CNFlowType.fixedRate) {
|
||||
params["useRateId"] = "true";
|
||||
}
|
||||
|
||||
final uri = _buildUriV2("/exchange/estimated-amount", params);
|
||||
|
||||
try {
|
||||
// simple json object is expected here
|
||||
final json = await _makeGetRequest(uri);
|
||||
final json = await _makeGetRequestV2(uri, apiKey ?? kChangeNowApiKey);
|
||||
|
||||
try {
|
||||
final value = EstimatedExchangeAmount.fromJson(
|
||||
Map<String, dynamic>.from(json as Map));
|
||||
final value =
|
||||
CNExchangeEstimate.fromJson(Map<String, dynamic>.from(json as Map));
|
||||
return ChangeNowResponse(value: value);
|
||||
} catch (_) {
|
||||
return ChangeNowResponse(
|
||||
|
@ -324,8 +423,7 @@ class ChangeNow {
|
|||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"getEstimatedFixedRateExchangeAmount exception: $e\n$s",
|
||||
Logging.instance.log("getEstimatedExchangeAmountV2 exception: $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
return ChangeNowResponse(
|
||||
exception: ChangeNowException(
|
||||
|
|
|
@ -43,7 +43,7 @@ import 'package:tuple/tuple.dart';
|
|||
import 'package:uuid/uuid.dart';
|
||||
|
||||
const int MINIMUM_CONFIRMATIONS = 2;
|
||||
const int DUST_LIMIT = 546;
|
||||
const int DUST_LIMIT = 294;
|
||||
|
||||
const String GENESIS_HASH_MAINNET =
|
||||
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
|
||||
|
@ -2715,11 +2715,11 @@ class BitcoinWallet extends CoinServiceAPI {
|
|||
|
||||
final txModel = TransactionData.fromMap(transactionsMap);
|
||||
|
||||
DB.instance.put<dynamic>(
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: walletId,
|
||||
key: 'storedTxnDataHeight',
|
||||
value: latestTxnBlockHeight);
|
||||
DB.instance.put<dynamic>(
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: walletId, key: 'latest_tx_model', value: txModel);
|
||||
|
||||
return txModel;
|
||||
|
@ -2897,7 +2897,7 @@ class BitcoinWallet extends CoinServiceAPI {
|
|||
int changeOutputSize =
|
||||
satoshisBeingUsed - satoshiAmountToSend - feeForTwoOutputs;
|
||||
// We check to see if the user can pay for the new transaction with 2 outputs instead of one. If they can and
|
||||
// the second output's size > 546 satoshis, we perform the mechanics required to properly generate and use a new
|
||||
// the second output's size > DUST_LIMIT satoshis, we perform the mechanics required to properly generate and use a new
|
||||
// change address.
|
||||
if (changeOutputSize > DUST_LIMIT &&
|
||||
satoshisBeingUsed - satoshiAmountToSend - changeOutputSize ==
|
||||
|
@ -2972,7 +2972,7 @@ class BitcoinWallet extends CoinServiceAPI {
|
|||
return transactionObject;
|
||||
} else {
|
||||
// Something went wrong here. It either overshot or undershot the estimated fee amount or the changeOutputSize
|
||||
// is smaller than or equal to 546. Revert to single output transaction.
|
||||
// is smaller than or equal to DUST_LIMIT. Revert to single output transaction.
|
||||
Logging.instance.log('1 output in tx', level: LogLevel.Info);
|
||||
Logging.instance
|
||||
.log('Input size: $satoshisBeingUsed', level: LogLevel.Info);
|
||||
|
@ -2999,7 +2999,7 @@ class BitcoinWallet extends CoinServiceAPI {
|
|||
return transactionObject;
|
||||
}
|
||||
} else {
|
||||
// No additional outputs needed since adding one would mean that it'd be smaller than 546 sats
|
||||
// No additional outputs needed since adding one would mean that it'd be smaller than DUST_LIMIT sats
|
||||
// which makes it uneconomical to add to the transaction. Here, we pass data directly to instruct
|
||||
// the wallet to begin crafting the transaction that the user requested.
|
||||
Logging.instance.log('1 output in tx', level: LogLevel.Info);
|
||||
|
@ -3825,4 +3825,34 @@ class BitcoinWallet extends CoinServiceAPI {
|
|||
|
||||
return available - estimatedFee;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> generateNewAddress() async {
|
||||
try {
|
||||
await _incrementAddressIndexForChain(
|
||||
0, DerivePathType.bip84); // First increment the receiving index
|
||||
final newReceivingIndex = DB.instance.get<dynamic>(
|
||||
boxName: walletId,
|
||||
key: 'receivingIndexP2WPKH') as int; // Check the new receiving index
|
||||
final newReceivingAddress = await _generateAddressForChain(
|
||||
0,
|
||||
newReceivingIndex,
|
||||
DerivePathType
|
||||
.bip84); // Use new index to derive a new receiving address
|
||||
await _addToAddressesArrayForChain(
|
||||
newReceivingAddress,
|
||||
0,
|
||||
DerivePathType
|
||||
.bip84); // Add that new receiving address to the array of receiving addresses
|
||||
_currentReceivingAddress = Future(() =>
|
||||
newReceivingAddress); // Set the new receiving address that the service
|
||||
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from generateNewAddress(): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -223,4 +223,6 @@ abstract class CoinServiceAPI {
|
|||
bool get isConnected;
|
||||
|
||||
Future<int> estimateFeeFor(int satoshiAmount, int feeRate);
|
||||
|
||||
Future<bool> generateNewAddress();
|
||||
}
|
||||
|
|
|
@ -3011,6 +3011,35 @@ class DogecoinWallet extends CoinServiceAPI {
|
|||
|
||||
return available - estimatedFee;
|
||||
}
|
||||
|
||||
Future<bool> generateNewAddress() async {
|
||||
try {
|
||||
await _incrementAddressIndexForChain(
|
||||
0, DerivePathType.bip44); // First increment the receiving index
|
||||
final newReceivingIndex = DB.instance.get<dynamic>(
|
||||
boxName: walletId,
|
||||
key: 'receivingIndexP2PKH') as int; // Check the new receiving index
|
||||
final newReceivingAddress = await _generateAddressForChain(
|
||||
0,
|
||||
newReceivingIndex,
|
||||
DerivePathType
|
||||
.bip44); // Use new index to derive a new receiving address
|
||||
await _addToAddressesArrayForChain(
|
||||
newReceivingAddress,
|
||||
0,
|
||||
DerivePathType
|
||||
.bip44); // Add that new receiving address to the array of receiving addresses
|
||||
_currentReceivingAddressP2PKH = Future(() =>
|
||||
newReceivingAddress); // Set the new receiving address that the service
|
||||
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from generateNewAddress(): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dogecoin Network
|
||||
|
|
|
@ -77,11 +77,8 @@ Future<void> executeNative(Map<String, dynamic> arguments) async {
|
|||
final startHeight = arguments['startHeight'] as int?;
|
||||
final numberOfBlocks = arguments['numberOfBlocks'] as int?;
|
||||
Map<String, dynamic> result = {};
|
||||
if (!(wallet == null ||
|
||||
startHeight == null ||
|
||||
numberOfBlocks == null)) {
|
||||
var outputs =
|
||||
await scanOutPuts(wallet, startHeight, numberOfBlocks);
|
||||
if (!(wallet == null || startHeight == null || numberOfBlocks == null)) {
|
||||
var outputs = await scanOutPuts(wallet, startHeight, numberOfBlocks);
|
||||
result['outputs'] = outputs;
|
||||
sendPort.send(result);
|
||||
return;
|
||||
|
@ -111,8 +108,8 @@ Future<void> executeNative(Map<String, dynamic> arguments) async {
|
|||
epicboxConfig == null)) {
|
||||
Logging.instance
|
||||
.log("SECRET_KEY_INDEX_IS $secretKeyIndex", level: LogLevel.Info);
|
||||
result['result'] = await getSubscribeRequest(
|
||||
wallet, secretKeyIndex, epicboxConfig);
|
||||
result['result'] =
|
||||
await getSubscribeRequest(wallet, secretKeyIndex, epicboxConfig);
|
||||
sendPort.send(result);
|
||||
return;
|
||||
}
|
||||
|
@ -122,8 +119,7 @@ Future<void> executeNative(Map<String, dynamic> arguments) async {
|
|||
Map<String, dynamic> result = {};
|
||||
|
||||
if (!(wallet == null || slates == null)) {
|
||||
result['result'] =
|
||||
await processSlates(wallet, slates.toString());
|
||||
result['result'] = await processSlates(wallet, slates.toString());
|
||||
sendPort.send(result);
|
||||
return;
|
||||
}
|
||||
|
@ -135,8 +131,8 @@ Future<void> executeNative(Map<String, dynamic> arguments) async {
|
|||
if (!(wallet == null ||
|
||||
refreshFromNode == null ||
|
||||
minimumConfirmations == null)) {
|
||||
var res = await getWalletInfo(
|
||||
wallet, refreshFromNode, minimumConfirmations);
|
||||
var res =
|
||||
await getWalletInfo(wallet, refreshFromNode, minimumConfirmations);
|
||||
result['result'] = res;
|
||||
sendPort.send(result);
|
||||
return;
|
||||
|
@ -166,11 +162,9 @@ Future<void> executeNative(Map<String, dynamic> arguments) async {
|
|||
final amount = arguments['amount'] as int?;
|
||||
final minimumConfirmations = arguments['minimumConfirmations'] as int?;
|
||||
Map<String, dynamic> result = {};
|
||||
if (!(wallet == null ||
|
||||
amount == null ||
|
||||
minimumConfirmations == null)) {
|
||||
var res = await getTransactionFees(
|
||||
wallet, amount, minimumConfirmations);
|
||||
if (!(wallet == null || amount == null || minimumConfirmations == null)) {
|
||||
var res =
|
||||
await getTransactionFees(wallet, amount, minimumConfirmations);
|
||||
result['result'] = res;
|
||||
sendPort.send(result);
|
||||
return;
|
||||
|
@ -198,7 +192,8 @@ Future<void> executeNative(Map<String, dynamic> arguments) async {
|
|||
}
|
||||
} else if (function == "txHttpSend") {
|
||||
final wallet = arguments['wallet'] as String?;
|
||||
final selectionStrategyIsAll = arguments['selectionStrategyIsAll'] as int?;
|
||||
final selectionStrategyIsAll =
|
||||
arguments['selectionStrategyIsAll'] as int?;
|
||||
final minimumConfirmations = arguments['minimumConfirmations'] as int?;
|
||||
final message = arguments['message'] as String?;
|
||||
final amount = arguments['amount'] as int?;
|
||||
|
@ -218,7 +213,6 @@ Future<void> executeNative(Map<String, dynamic> arguments) async {
|
|||
sendPort.send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
Logging.instance.log(
|
||||
"Error Arguments for $function not formatted correctly",
|
||||
|
@ -245,8 +239,7 @@ void stop(ReceivePort port) {
|
|||
|
||||
// Keep Wrapper functions outside of the class to avoid memory leaks and errors about receive ports and illegal arguments.
|
||||
// TODO: Can get rid of this wrapper and call it in a full isolate instead of compute() if we want more control over this
|
||||
Future<String> _cancelTransactionWrapper(
|
||||
Tuple2<String, String> data) async {
|
||||
Future<String> _cancelTransactionWrapper(Tuple2<String, String> data) async {
|
||||
// assuming this returns an empty string on success
|
||||
// or an error message string on failure
|
||||
return cancelTransaction(data.item1, data.item2);
|
||||
|
@ -290,8 +283,7 @@ Future<String> _initWalletWrapper(
|
|||
|
||||
Future<String> _initGetAddressInfoWrapper(
|
||||
Tuple3<String, int, String> data) async {
|
||||
String walletAddress =
|
||||
getAddressInfo(data.item1, data.item2, data.item3);
|
||||
String walletAddress = getAddressInfo(data.item1, data.item2, data.item3);
|
||||
return walletAddress;
|
||||
}
|
||||
|
||||
|
@ -727,7 +719,7 @@ class EpicCashWallet extends CoinServiceAPI {
|
|||
/// returns an empty String on success, error message on failure
|
||||
Future<String> cancelPendingTransaction(String tx_slate_id) async {
|
||||
final String wallet =
|
||||
(await _secureStore.read(key: '${_walletId}_wallet'))!;
|
||||
(await _secureStore.read(key: '${_walletId}_wallet'))!;
|
||||
|
||||
String? result;
|
||||
await m.protect(() async {
|
||||
|
@ -754,7 +746,8 @@ class EpicCashWallet extends CoinServiceAPI {
|
|||
|
||||
String receiverAddress = txData['addresss'] as String;
|
||||
await m.protect(() async {
|
||||
if (receiverAddress.startsWith("http://") || receiverAddress.startsWith("https://")) {
|
||||
if (receiverAddress.startsWith("http://") ||
|
||||
receiverAddress.startsWith("https://")) {
|
||||
const int selectionStrategyIsAll = 0;
|
||||
ReceivePort receivePort = await getIsolate({
|
||||
"function": "txHttpSend",
|
||||
|
@ -774,9 +767,8 @@ class EpicCashWallet extends CoinServiceAPI {
|
|||
throw Exception("txHttpSend isolate failed");
|
||||
}
|
||||
stop(receivePort);
|
||||
Logging.instance.log('Closing txHttpSend!\n $message',
|
||||
level: LogLevel.Info);
|
||||
|
||||
Logging.instance
|
||||
.log('Closing txHttpSend!\n $message', level: LogLevel.Info);
|
||||
} else {
|
||||
ReceivePort receivePort = await getIsolate({
|
||||
"function": "createTransaction",
|
||||
|
@ -809,7 +801,6 @@ class EpicCashWallet extends CoinServiceAPI {
|
|||
|
||||
await putSendToAddresses(sendTx);
|
||||
|
||||
|
||||
Logging.instance.log("CONFIRM_RESULT_IS $sendTx", level: LogLevel.Info);
|
||||
|
||||
final decodeData = json.decode(sendTx);
|
||||
|
@ -818,9 +809,9 @@ class EpicCashWallet extends CoinServiceAPI {
|
|||
String errorMessage = decodeData[1] as String;
|
||||
throw Exception("Transaction failed with error code $errorMessage");
|
||||
} else {
|
||||
|
||||
//If it's HTTP send no need to post to epicbox
|
||||
if (!(receiverAddress.startsWith("http://") || receiverAddress.startsWith("https://"))) {
|
||||
if (!(receiverAddress.startsWith("http://") ||
|
||||
receiverAddress.startsWith("https://"))) {
|
||||
final postSlateRequest = decodeData[1];
|
||||
final postToServer = await postSlate(
|
||||
txData['addresss'] as String, postSlateRequest as String);
|
||||
|
@ -969,10 +960,9 @@ class EpicCashWallet extends CoinServiceAPI {
|
|||
level: LogLevel.Info);
|
||||
|
||||
final config = await getRealConfig();
|
||||
final password =
|
||||
await _secureStore.read(key: '${_walletId}_password');
|
||||
final password = await _secureStore.read(key: '${_walletId}_password');
|
||||
|
||||
final walletOpen = openWallet(config!, password!);
|
||||
final walletOpen = openWallet(config, password!);
|
||||
await _secureStore.write(key: '${_walletId}_wallet', value: walletOpen);
|
||||
|
||||
if ((DB.instance.get<dynamic>(boxName: walletId, key: "id")) == null) {
|
||||
|
@ -1297,7 +1287,6 @@ class EpicCashWallet extends CoinServiceAPI {
|
|||
|
||||
Future<bool> startScans() async {
|
||||
try {
|
||||
|
||||
final wallet = await _secureStore.read(key: '${_walletId}_wallet');
|
||||
|
||||
var restoreHeight =
|
||||
|
@ -1445,7 +1434,6 @@ class EpicCashWallet extends CoinServiceAPI {
|
|||
|
||||
//Store Epic box address info
|
||||
await storeEpicboxInfo();
|
||||
|
||||
} catch (e, s) {
|
||||
Logging.instance
|
||||
.log("Error recovering wallet $e\n$s", level: LogLevel.Error);
|
||||
|
@ -1724,11 +1712,13 @@ class EpicCashWallet extends CoinServiceAPI {
|
|||
subscribeRequest['signature'] as String, slate as String);
|
||||
}
|
||||
|
||||
if (response.contains("Error Wallet store error: DB Not Found Error")) {
|
||||
if (response
|
||||
.contains("Error Wallet store error: DB Not Found Error")) {
|
||||
//Already processed - to be deleted
|
||||
Logging.instance.log("DELETING_PROCESSED_SLATE",
|
||||
level: LogLevel.Info);
|
||||
final slateDelete = await deleteSlate(currentAddress, subscribeRequest['signature'] as String, slate as String);
|
||||
Logging.instance
|
||||
.log("DELETING_PROCESSED_SLATE", level: LogLevel.Info);
|
||||
final slateDelete = await deleteSlate(currentAddress,
|
||||
subscribeRequest['signature'] as String, slate as String);
|
||||
Logging.instance.log("DELETE_SLATE_RESPONSE $slateDelete",
|
||||
level: LogLevel.Info);
|
||||
} else {
|
||||
|
@ -1738,14 +1728,14 @@ class EpicCashWallet extends CoinServiceAPI {
|
|||
if (slateStatus == "PendingProcessing") {
|
||||
//Encrypt slate
|
||||
String encryptedSlate = await getEncryptedSlate(
|
||||
wallet!,
|
||||
wallet,
|
||||
slateSender,
|
||||
currentReceivingIndex,
|
||||
epicboxConfig!,
|
||||
decodedResponse[1] as String);
|
||||
|
||||
final postSlateToServer =
|
||||
await postSlate(slateSender, encryptedSlate);
|
||||
await postSlate(slateSender, encryptedSlate);
|
||||
|
||||
await deleteSlate(currentAddress,
|
||||
subscribeRequest['signature'] as String, slate as String);
|
||||
|
@ -1753,7 +1743,8 @@ class EpicCashWallet extends CoinServiceAPI {
|
|||
level: LogLevel.Info);
|
||||
} else {
|
||||
//Finalise Slate
|
||||
final processSlate = json.decode(decodedResponse[1] as String);
|
||||
final processSlate =
|
||||
json.decode(decodedResponse[1] as String);
|
||||
Logging.instance.log(
|
||||
"PROCESSED_SLATE_TO_FINALIZE $processSlate",
|
||||
level: LogLevel.Info);
|
||||
|
@ -1762,8 +1753,7 @@ class EpicCashWallet extends CoinServiceAPI {
|
|||
String txSlateId = tx[0]['tx_slate_id'] as String;
|
||||
Logging.instance
|
||||
.log("TX_SLATE_ID_IS $txSlateId", level: LogLevel.Info);
|
||||
final postToNode = await postSlateToNode(
|
||||
wallet!, txSlateId);
|
||||
final postToNode = await postSlateToNode(wallet, txSlateId);
|
||||
await deleteSlate(currentAddress,
|
||||
subscribeRequest['signature'] as String, slate as String);
|
||||
Logging.instance.log("POST_SLATE_RESPONSE $postToNode",
|
||||
|
@ -2316,4 +2306,29 @@ class EpicCashWallet extends CoinServiceAPI {
|
|||
// TODO: implement this
|
||||
return currentFee;
|
||||
}
|
||||
|
||||
// not used in epic currently
|
||||
@override
|
||||
Future<bool> generateNewAddress() async {
|
||||
try {
|
||||
// await incrementAddressIndexForChain(
|
||||
// 0); // First increment the receiving index
|
||||
// final newReceivingIndex =
|
||||
// DB.instance.get<dynamic>(boxName: walletId, key: 'receivingIndex')
|
||||
// as int; // Check the new receiving index
|
||||
// final newReceivingAddress = await _generateAddressForChain(0,
|
||||
// newReceivingIndex); // Use new index to derive a new receiving address
|
||||
// await addToAddressesArrayForChain(newReceivingAddress,
|
||||
// 0); // Add that new receiving address to the array of receiving addresses
|
||||
// _currentReceivingAddress = Future(() =>
|
||||
// newReceivingAddress); // Set the new receiving address that the service
|
||||
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from generateNewAddress(): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -266,4 +266,12 @@ class Manager with ChangeNotifier {
|
|||
Future<int> estimateFeeFor(int satoshiAmount, int feeRate) async {
|
||||
return _currentWallet.estimateFeeFor(satoshiAmount, feeRate);
|
||||
}
|
||||
|
||||
Future<bool> generateNewAddress() async {
|
||||
final success = await _currentWallet.generateNewAddress();
|
||||
if (success) {
|
||||
notifyListeners();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1521,4 +1521,33 @@ class MoneroWallet extends CoinServiceAPI {
|
|||
10000;
|
||||
return fee;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> generateNewAddress() async {
|
||||
try {
|
||||
const String indexKey = "receivingIndex";
|
||||
// First increment the receiving index
|
||||
await _incrementAddressIndexForChain(0);
|
||||
final newReceivingIndex =
|
||||
DB.instance.get<dynamic>(boxName: walletId, key: indexKey) as int;
|
||||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress =
|
||||
await _generateAddressForChain(0, newReceivingIndex);
|
||||
|
||||
// Add that new receiving address to the array of receiving addresses
|
||||
await _addToAddressesArrayForChain(newReceivingAddress, 0);
|
||||
|
||||
// Set the new receiving address that the service
|
||||
|
||||
_currentReceivingAddress = Future(() => newReceivingAddress);
|
||||
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from generateNewAddress(): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,75 +103,79 @@ class NotificationsService extends ChangeNotifier {
|
|||
|
||||
void _checkTransactions() async {
|
||||
for (final notification in _watchedTransactionNotifications) {
|
||||
final Coin coin = coinFromPrettyName(notification.coinName);
|
||||
final txid = notification.txid!;
|
||||
try {
|
||||
final Coin coin = coinFromPrettyName(notification.coinName);
|
||||
final txid = notification.txid!;
|
||||
|
||||
final node = nodeService.getPrimaryNodeFor(coin: coin);
|
||||
if (node != null) {
|
||||
if (coin.isElectrumXCoin) {
|
||||
final eNode = ElectrumXNode(
|
||||
address: node.host,
|
||||
port: node.port,
|
||||
name: node.name,
|
||||
id: node.id,
|
||||
useSSL: node.useSSL,
|
||||
);
|
||||
final failovers = nodeService
|
||||
.failoverNodesFor(coin: coin)
|
||||
.map((e) => ElectrumXNode(
|
||||
address: e.host,
|
||||
port: e.port,
|
||||
name: e.name,
|
||||
id: e.id,
|
||||
useSSL: e.useSSL,
|
||||
))
|
||||
.toList();
|
||||
|
||||
final client = ElectrumX.from(
|
||||
node: eNode,
|
||||
failovers: failovers,
|
||||
prefs: prefs,
|
||||
);
|
||||
final tx = await client.getTransaction(txHash: txid);
|
||||
|
||||
int confirmations = tx["confirmations"] as int? ?? 0;
|
||||
|
||||
bool shouldWatchForUpdates = true;
|
||||
// check if the number of confirmations is greater than the number
|
||||
// required by the wallet to count the tx as confirmed and update the
|
||||
// flag on whether this notification should still be monitored
|
||||
if (confirmations >= coin.requiredConfirmations) {
|
||||
shouldWatchForUpdates = false;
|
||||
confirmations = coin.requiredConfirmations;
|
||||
}
|
||||
|
||||
// grab confirms string to compare
|
||||
final String newConfirms =
|
||||
"($confirmations/${coin.requiredConfirmations})";
|
||||
final String oldConfirms =
|
||||
notification.title.substring(notification.title.lastIndexOf("("));
|
||||
|
||||
// only update if they don't match
|
||||
if (oldConfirms != newConfirms) {
|
||||
final String newTitle =
|
||||
notification.title.replaceFirst(oldConfirms, newConfirms);
|
||||
|
||||
final updatedNotification = notification.copyWith(
|
||||
title: newTitle,
|
||||
shouldWatchForUpdates: shouldWatchForUpdates,
|
||||
final node = nodeService.getPrimaryNodeFor(coin: coin);
|
||||
if (node != null) {
|
||||
if (coin.isElectrumXCoin) {
|
||||
final eNode = ElectrumXNode(
|
||||
address: node.host,
|
||||
port: node.port,
|
||||
name: node.name,
|
||||
id: node.id,
|
||||
useSSL: node.useSSL,
|
||||
);
|
||||
final failovers = nodeService
|
||||
.failoverNodesFor(coin: coin)
|
||||
.map((e) => ElectrumXNode(
|
||||
address: e.host,
|
||||
port: e.port,
|
||||
name: e.name,
|
||||
id: e.id,
|
||||
useSSL: e.useSSL,
|
||||
))
|
||||
.toList();
|
||||
|
||||
// remove from watch list if shouldWatchForUpdates was changed
|
||||
if (!shouldWatchForUpdates) {
|
||||
await _deleteWatchedTxNotification(notification);
|
||||
final client = ElectrumX.from(
|
||||
node: eNode,
|
||||
failovers: failovers,
|
||||
prefs: prefs,
|
||||
);
|
||||
final tx = await client.getTransaction(txHash: txid);
|
||||
|
||||
int confirmations = tx["confirmations"] as int? ?? 0;
|
||||
|
||||
bool shouldWatchForUpdates = true;
|
||||
// check if the number of confirmations is greater than the number
|
||||
// required by the wallet to count the tx as confirmed and update the
|
||||
// flag on whether this notification should still be monitored
|
||||
if (confirmations >= coin.requiredConfirmations) {
|
||||
shouldWatchForUpdates = false;
|
||||
confirmations = coin.requiredConfirmations;
|
||||
}
|
||||
|
||||
// replaces the current notification with the updated one
|
||||
add(updatedNotification, true);
|
||||
// grab confirms string to compare
|
||||
final String newConfirms =
|
||||
"($confirmations/${coin.requiredConfirmations})";
|
||||
final String oldConfirms = notification.title
|
||||
.substring(notification.title.lastIndexOf("("));
|
||||
|
||||
// only update if they don't match
|
||||
if (oldConfirms != newConfirms) {
|
||||
final String newTitle =
|
||||
notification.title.replaceFirst(oldConfirms, newConfirms);
|
||||
|
||||
final updatedNotification = notification.copyWith(
|
||||
title: newTitle,
|
||||
shouldWatchForUpdates: shouldWatchForUpdates,
|
||||
);
|
||||
|
||||
// remove from watch list if shouldWatchForUpdates was changed
|
||||
if (!shouldWatchForUpdates) {
|
||||
await _deleteWatchedTxNotification(notification);
|
||||
}
|
||||
|
||||
// replaces the current notification with the updated one
|
||||
add(updatedNotification, true);
|
||||
}
|
||||
} else {
|
||||
// TODO: check non electrumx coins
|
||||
}
|
||||
} else {
|
||||
// TODO: check non electrumx coins
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e $s", level: LogLevel.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,6 +126,17 @@ class WalletsService extends ChangeNotifier {
|
|||
}
|
||||
Logging.instance.log("Fetched wallet names: $names", level: LogLevel.Info);
|
||||
final mapped = Map<String, dynamic>.from(names);
|
||||
mapped.removeWhere((name, dyn) {
|
||||
final jsonObject = Map<String, dynamic>.from(dyn as Map);
|
||||
try {
|
||||
Coin.values.byName(jsonObject["coin"] as String);
|
||||
return false;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Error, ${jsonObject["coin"]} does not exist",
|
||||
level: LogLevel.Error);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return mapped.map((name, dyn) => MapEntry(
|
||||
name, WalletInfo.fromJson(Map<String, dynamic>.from(dyn as Map))));
|
||||
|
|
|
@ -84,6 +84,10 @@ class _SVG {
|
|||
String get solidSliders => "assets/svg/sliders-solid.svg";
|
||||
String get questionMessage => "assets/svg/message-question.svg";
|
||||
String get envelope => "assets/svg/envelope.svg";
|
||||
String get share => "assets/svg/share-2.svg";
|
||||
String get anonymize => "assets/svg/tx-icon-anonymize.svg";
|
||||
String get anonymizePending => "assets/svg/tx-icon-anonymize-pending.svg";
|
||||
String get anonymizeFailed => "assets/svg/tx-icon-anonymize-failed.svg";
|
||||
|
||||
String get receive => "assets/svg/tx-icon-receive.svg";
|
||||
String get receivePending => "assets/svg/tx-icon-receive-pending.svg";
|
||||
|
|
|
@ -9,8 +9,8 @@ class _CoinThemeColor {
|
|||
Color get bitcoincash => const Color(0xFFFCC17B);
|
||||
Color get firo => const Color(0xFFFF897A);
|
||||
Color get dogecoin => const Color(0xFFFFE079);
|
||||
Color get epicCash => const Color(0xFFC1C1FF);
|
||||
Color get monero => const Color(0xFFB1C5FF);
|
||||
Color get epicCash => const Color(0xFFC5C7CB);
|
||||
Color get monero => const Color(0xFFFF9E6B);
|
||||
|
||||
Color forCoin(Coin coin) {
|
||||
switch (coin) {
|
||||
|
|
|
@ -33,7 +33,7 @@ abstract class Constants {
|
|||
// Enable Logger.print statements
|
||||
static const bool disableLogger = false;
|
||||
|
||||
static const int currentDbVersion = 0;
|
||||
static const int currentHiveDbVersion = 1;
|
||||
|
||||
static List<int> possibleLengthsForCoin(Coin coin) {
|
||||
final List<int> values = [];
|
||||
|
|
|
@ -1,5 +1,16 @@
|
|||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||
import 'package:stackwallet/hive/db.dart';
|
||||
import 'package:stackwallet/models/lelantus_coin.dart';
|
||||
import 'package:stackwallet/models/node_model.dart';
|
||||
import 'package:stackwallet/services/node_service.dart';
|
||||
import 'package:stackwallet/services/wallets_service.dart';
|
||||
import 'package:stackwallet/utilities/default_nodes.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/prefs.dart';
|
||||
|
||||
class DbVersionMigrator {
|
||||
Future<void> migrate(
|
||||
|
@ -8,83 +19,125 @@ class DbVersionMigrator {
|
|||
FlutterSecureStorage(),
|
||||
),
|
||||
}) async {
|
||||
// final wallets = await Hive.openBox('wallets');
|
||||
// final names = Map<String, String>.from((await wallets.get("names")) ?? {});
|
||||
//
|
||||
// switch (fromVersion) {
|
||||
// case 0:
|
||||
// // migrate each
|
||||
// for (final entry in names.entries) {
|
||||
// final walletId = entry.value;
|
||||
// final walletName = entry.key;
|
||||
//
|
||||
// // move main/test network to walletId based
|
||||
// final network = await wallets.get("${entry.key}_network");
|
||||
// await wallets.put("${walletId}_network", network);
|
||||
// await wallets.delete("${walletName}_network");
|
||||
//
|
||||
// final old = await Hive.openBox(walletName);
|
||||
// final wallet = await Hive.openBox(walletId);
|
||||
//
|
||||
// // notes
|
||||
// final oldNotes = await old.get("notes");
|
||||
// await wallet.put("notes", oldNotes);
|
||||
// await old.delete("notes");
|
||||
//
|
||||
// // address book
|
||||
// final addressBook = await old.get("addressBookEntries");
|
||||
// await wallet.put("addressBookEntries", addressBook);
|
||||
// await old.put("addressBookEntries", null);
|
||||
//
|
||||
// // receiveDerivations
|
||||
// Map<String, dynamic> newReceiveDerivations = {};
|
||||
// final receiveDerivations =
|
||||
// Map<int, dynamic>.from(await old.get("receiveDerivations") ?? {});
|
||||
//
|
||||
// for (int i = 0; i < receiveDerivations.length; i++) {
|
||||
// receiveDerivations[i].remove("fingerprint");
|
||||
// receiveDerivations[i].remove("identifier");
|
||||
// receiveDerivations[i].remove("privateKey");
|
||||
// newReceiveDerivations["$i"] = receiveDerivations[i];
|
||||
// }
|
||||
// final receiveDerivationsString = jsonEncode(newReceiveDerivations);
|
||||
//
|
||||
// await secureStore.write(
|
||||
// key: "${walletId}_receiveDerivations",
|
||||
// value: receiveDerivationsString);
|
||||
// await old.delete("receiveDerivations");
|
||||
//
|
||||
// // changeDerivations
|
||||
// Map<String, dynamic> newChangeDerivations = {};
|
||||
// final changeDerivations =
|
||||
// Map<int, dynamic>.from(await old.get("changeDerivations") ?? {});
|
||||
//
|
||||
// for (int i = 0; i < changeDerivations.length; i++) {
|
||||
// changeDerivations[i].remove("fingerprint");
|
||||
// changeDerivations[i].remove("identifier");
|
||||
// changeDerivations[i].remove("privateKey");
|
||||
// newChangeDerivations["$i"] = changeDerivations[i];
|
||||
// }
|
||||
// final changeDerivationsString = jsonEncode(newChangeDerivations);
|
||||
//
|
||||
// await secureStore.write(
|
||||
// key: "${walletId}_changeDerivations",
|
||||
// value: changeDerivationsString);
|
||||
// await old.delete("changeDerivations");
|
||||
// }
|
||||
//
|
||||
// // finally update version
|
||||
// await wallets.put("db_version", 1);
|
||||
//
|
||||
// return;
|
||||
// // not needed yet
|
||||
// // return migrate(1);
|
||||
//
|
||||
// // case 1:
|
||||
// // return migrate(2);
|
||||
//
|
||||
// default:
|
||||
// return;
|
||||
// }
|
||||
switch (fromVersion) {
|
||||
case 0:
|
||||
await Hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
await Hive.openBox<dynamic>(DB.boxNamePrefs);
|
||||
final walletsService = WalletsService();
|
||||
final nodeService = NodeService();
|
||||
final prefs = Prefs.instance;
|
||||
final walletInfoList = await walletsService.walletNames;
|
||||
await prefs.init();
|
||||
|
||||
ElectrumX? client;
|
||||
int? latestSetId;
|
||||
|
||||
// only instantiate client if there are firo wallets
|
||||
if (walletInfoList.values.any((element) => element.coin == Coin.firo)) {
|
||||
await Hive.openBox<NodeModel>(DB.boxNameNodeModels);
|
||||
await Hive.openBox<NodeModel>(DB.boxNamePrimaryNodes);
|
||||
final node = nodeService.getPrimaryNodeFor(coin: Coin.firo) ??
|
||||
DefaultNodes.firo;
|
||||
List<ElectrumXNode> failovers = nodeService
|
||||
.failoverNodesFor(coin: Coin.firo)
|
||||
.map(
|
||||
(e) => ElectrumXNode(
|
||||
address: e.host,
|
||||
port: e.port,
|
||||
name: e.name,
|
||||
id: e.id,
|
||||
useSSL: e.useSSL,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
client = ElectrumX.from(
|
||||
node: ElectrumXNode(
|
||||
address: node.host,
|
||||
port: node.port,
|
||||
name: node.name,
|
||||
id: node.id,
|
||||
useSSL: node.useSSL),
|
||||
prefs: prefs,
|
||||
failovers: failovers,
|
||||
);
|
||||
|
||||
try {
|
||||
latestSetId = await client.getLatestCoinId();
|
||||
} catch (e) {
|
||||
// default to 2 for now
|
||||
latestSetId = 2;
|
||||
Logging.instance.log(
|
||||
"Failed to fetch latest coin id during firo db migrate: $e \nUsing a default value of 2",
|
||||
level: LogLevel.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
for (final walletInfo in walletInfoList.values) {
|
||||
// migrate each firo wallet's lelantus coins
|
||||
if (walletInfo.coin == Coin.firo) {
|
||||
await Hive.openBox<dynamic>(walletInfo.walletId);
|
||||
final _lelantusCoins = DB.instance.get<dynamic>(
|
||||
boxName: walletInfo.walletId, key: '_lelantus_coins') as List?;
|
||||
final List<Map<dynamic, LelantusCoin>> lelantusCoins = [];
|
||||
for (var lCoin in _lelantusCoins ?? []) {
|
||||
lelantusCoins
|
||||
.add({lCoin.keys.first: lCoin.values.first as LelantusCoin});
|
||||
}
|
||||
|
||||
List<Map<dynamic, LelantusCoin>> coins = [];
|
||||
for (final element in lelantusCoins) {
|
||||
LelantusCoin coin = element.values.first;
|
||||
int anonSetId = coin.anonymitySetId;
|
||||
if (coin.anonymitySetId == 1 &&
|
||||
(coin.publicCoin == '' ||
|
||||
coin.publicCoin == "jmintData.publicCoin")) {
|
||||
anonSetId = latestSetId!;
|
||||
}
|
||||
coins.add({
|
||||
element.keys.first: LelantusCoin(coin.index, coin.value,
|
||||
coin.publicCoin, coin.txId, anonSetId, coin.isUsed)
|
||||
});
|
||||
}
|
||||
Logger.print("newcoins $coins", normalLength: false);
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: walletInfo.walletId,
|
||||
key: '_lelantus_coins',
|
||||
value: coins);
|
||||
}
|
||||
}
|
||||
|
||||
// update version
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 1);
|
||||
|
||||
// try to continue migrating
|
||||
return await migrate(1);
|
||||
|
||||
// case 1:
|
||||
// await Hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
// final walletsService = WalletsService();
|
||||
// final walletInfoList = await walletsService.walletNames;
|
||||
// for (final walletInfo in walletInfoList.values) {
|
||||
// if (walletInfo.coin == Coin.firo) {
|
||||
// await Hive.openBox<dynamic>(walletInfo.walletId);
|
||||
// await DB.instance.delete<dynamic>(
|
||||
// key: "latest_tx_model", boxName: walletInfo.walletId);
|
||||
// await DB.instance.delete<dynamic>(
|
||||
// key: "latest_lelantus_tx_model", boxName: walletInfo.walletId);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // update version
|
||||
// await DB.instance.put<dynamic>(
|
||||
// boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 2);
|
||||
//
|
||||
// // try to continue migrating
|
||||
// return await migrate(2);
|
||||
|
||||
default:
|
||||
// finally return
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ class Prefs extends ChangeNotifier {
|
|||
_autoBackupLocation = await _getAutoBackupLocation();
|
||||
_backupFrequencyType = await _getBackupFrequencyType();
|
||||
_lastAutoBackup = await _getLastAutoBackup();
|
||||
_hideBlockExplorerWarning = await _getHideBlockExplorerWarning();
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
@ -466,4 +467,32 @@ class Prefs extends ChangeNotifier {
|
|||
return await DB.instance.get<dynamic>(
|
||||
boxName: DB.boxNamePrefs, key: "autoBackupFileUri") as DateTime?;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// auto backup
|
||||
|
||||
bool _hideBlockExplorerWarning = false;
|
||||
|
||||
bool get hideBlockExplorerWarning => _hideBlockExplorerWarning;
|
||||
|
||||
set hideBlockExplorerWarning(bool hideBlockExplorerWarning) {
|
||||
if (_hideBlockExplorerWarning != hideBlockExplorerWarning) {
|
||||
DB.instance
|
||||
.put<dynamic>(
|
||||
boxName: DB.boxNamePrefs,
|
||||
key: "hideBlockExplorerWarning",
|
||||
value: hideBlockExplorerWarning)
|
||||
.then((_) {
|
||||
_hideBlockExplorerWarning = hideBlockExplorerWarning;
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _getHideBlockExplorerWarning() async {
|
||||
return await DB.instance.get<dynamic>(
|
||||
boxName: DB.boxNamePrefs, key: "hideBlockExplorerWarning") as bool? ??
|
||||
false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/models/paymint/transactions_model.dart';
|
||||
|
@ -35,10 +37,30 @@ class _TransactionCardState extends ConsumerState<TransactionCard> {
|
|||
if (coin == Coin.epicCash && _transaction.slateId == null) {
|
||||
return "Restored Funds";
|
||||
}
|
||||
|
||||
if (_transaction.subType == "mint") {
|
||||
// if (type == "Received") {
|
||||
if (_transaction.confirmedStatus) {
|
||||
return "Anonymized";
|
||||
} else {
|
||||
return "Anonymizing";
|
||||
}
|
||||
// } else if (type == "Sent") {
|
||||
// if (_transaction.confirmedStatus) {
|
||||
// return "Sent MINT";
|
||||
// } else {
|
||||
// return "Sending MINT";
|
||||
// }
|
||||
// } else {
|
||||
// return type;
|
||||
// }
|
||||
}
|
||||
|
||||
if (type == "Received") {
|
||||
if (_transaction.isMinting) {
|
||||
return "Minting";
|
||||
} else if (_transaction.confirmedStatus) {
|
||||
// if (_transaction.isMinting) {
|
||||
// return "Minting";
|
||||
// } else
|
||||
if (_transaction.confirmedStatus) {
|
||||
return "Received";
|
||||
} else {
|
||||
return "Receiving";
|
||||
|
@ -95,23 +117,23 @@ class _TransactionCardState extends ConsumerState<TransactionCard> {
|
|||
),
|
||||
onPressed: () async {
|
||||
if (coin == Coin.epicCash && _transaction.slateId == null) {
|
||||
showFloatingFlushBar(
|
||||
unawaited(showFloatingFlushBar(
|
||||
context: context,
|
||||
message:
|
||||
"Restored Epic funds from your Seed have no Data.\nUse Stack Backup to keep your transaction history.",
|
||||
type: FlushBarType.warning,
|
||||
duration: const Duration(seconds: 5),
|
||||
);
|
||||
));
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pushNamed(
|
||||
unawaited(Navigator.of(context).pushNamed(
|
||||
TransactionDetailsView.routeName,
|
||||
arguments: Tuple3(
|
||||
_transaction,
|
||||
coin,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
));
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
|
|
|
@ -11,7 +11,7 @@ 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.4.39+51
|
||||
version: 1.4.42+56
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
|
@ -260,6 +260,10 @@ flutter:
|
|||
- assets/svg/sliders-solid.svg
|
||||
- assets/svg/message-question.svg
|
||||
- assets/svg/envelope.svg
|
||||
- assets/svg/share-2.svg
|
||||
- assets/svg/tx-icon-anonymize.svg
|
||||
- assets/svg/tx-icon-anonymize-pending.svg
|
||||
- assets/svg/tx-icon-anonymize-failed.svg
|
||||
# coin icons
|
||||
- assets/svg/coin_icons/Bitcoin.svg
|
||||
- assets/svg/coin_icons/Bitcoincash.svg
|
||||
|
|
|
@ -10,7 +10,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
|||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
|
||||
import 'cached_electrumx_test.mocks.dart';
|
||||
import 'sample_data/get_anonymity_set_sample_data.dart';
|
||||
// import 'sample_data/get_anonymity_set_sample_data.dart';
|
||||
|
||||
@GenerateMocks([ElectrumX, Prefs])
|
||||
void main() {
|
||||
|
@ -23,36 +23,36 @@ void main() {
|
|||
await Hive.openBox<dynamic>(DB.instance.boxNameTxCache(coin: Coin.firo));
|
||||
});
|
||||
group("getAnonymitySet", () {
|
||||
test("empty set cache call", () async {
|
||||
final client = MockElectrumX();
|
||||
when(
|
||||
client.getAnonymitySet(
|
||||
groupId: "1",
|
||||
blockhash: "",
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => GetAnonymitySetSampleData.data,
|
||||
);
|
||||
|
||||
final cachedClient = CachedElectrumX(
|
||||
electrumXClient: client,
|
||||
port: 0,
|
||||
failovers: [],
|
||||
server: '',
|
||||
useSSL: true,
|
||||
prefs: Prefs.instance);
|
||||
|
||||
final result = await cachedClient.getAnonymitySet(
|
||||
groupId: "1",
|
||||
coin: Coin.firo,
|
||||
);
|
||||
|
||||
final expected =
|
||||
Map<String, dynamic>.from(GetAnonymitySetSampleData.data);
|
||||
expected["setId"] = "1";
|
||||
|
||||
expect(result, expected);
|
||||
});
|
||||
// test("empty set cache call", () async {
|
||||
// final client = MockElectrumX();
|
||||
// when(
|
||||
// client.getAnonymitySet(
|
||||
// groupId: "1",
|
||||
// blockhash: "",
|
||||
// ),
|
||||
// ).thenAnswer(
|
||||
// (_) async => GetAnonymitySetSampleData.data,
|
||||
// );
|
||||
//
|
||||
// final cachedClient = CachedElectrumX(
|
||||
// electrumXClient: client,
|
||||
// port: 0,
|
||||
// failovers: [],
|
||||
// server: '',
|
||||
// useSSL: true,
|
||||
// prefs: Prefs.instance);
|
||||
//
|
||||
// final result = await cachedClient.getAnonymitySet(
|
||||
// groupId: "1",
|
||||
// coin: Coin.firo,
|
||||
// );
|
||||
//
|
||||
// final expected =
|
||||
// Map<String, dynamic>.from(GetAnonymitySetSampleData.data);
|
||||
// expected["setId"] = "1";
|
||||
//
|
||||
// expect(result, expected);
|
||||
// });
|
||||
//
|
||||
// test("use and update set cache call", () async {
|
||||
// final storedData = Map.from(GetAnonymitySetSampleData.initialData);
|
||||
|
@ -91,30 +91,30 @@ void main() {
|
|||
// fail("This test needs updating");
|
||||
// });
|
||||
|
||||
test("getAnonymitySet throws", () async {
|
||||
final client = MockElectrumX();
|
||||
when(
|
||||
client.getAnonymitySet(
|
||||
groupId: "1",
|
||||
blockhash: "",
|
||||
),
|
||||
).thenThrow(Exception());
|
||||
|
||||
final cachedClient = CachedElectrumX(
|
||||
electrumXClient: client,
|
||||
port: 0,
|
||||
failovers: [],
|
||||
server: '',
|
||||
useSSL: true,
|
||||
prefs: Prefs.instance);
|
||||
|
||||
expect(
|
||||
() async => await cachedClient.getAnonymitySet(
|
||||
groupId: "1",
|
||||
coin: Coin.firo,
|
||||
),
|
||||
throwsA(isA<Exception>()));
|
||||
});
|
||||
// test("getAnonymitySet throws", () async {
|
||||
// final client = MockElectrumX();
|
||||
// when(
|
||||
// client.getAnonymitySet(
|
||||
// groupId: "1",
|
||||
// blockhash: "",
|
||||
// ),
|
||||
// ).thenThrow(Exception());
|
||||
//
|
||||
// final cachedClient = CachedElectrumX(
|
||||
// electrumXClient: client,
|
||||
// port: 0,
|
||||
// failovers: [],
|
||||
// server: '',
|
||||
// useSSL: true,
|
||||
// prefs: Prefs.instance);
|
||||
//
|
||||
// expect(
|
||||
// () async => await cachedClient.getAnonymitySet(
|
||||
// groupId: "1",
|
||||
// coin: Coin.firo,
|
||||
// ),
|
||||
// throwsA(isA<Exception>()));
|
||||
// });
|
||||
});
|
||||
|
||||
test("getTransaction throws", () async {
|
||||
|
|
69
test/hive/db_test.dart
Normal file
69
test/hive/db_test.dart
Normal file
|
@ -0,0 +1,69 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hive_test/hive_test.dart';
|
||||
import 'package:stackwallet/hive/db.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
||||
void main() {
|
||||
group("DB box names", () {
|
||||
test("address book", () => expect(DB.boxNameAddressBook, "addressBook"));
|
||||
test("debug info", () => expect(DB.boxNameDebugInfo, "debugInfoBox"));
|
||||
test("nodes", () => expect(DB.boxNameNodeModels, "nodeModels"));
|
||||
test("primary nodes", () => expect(DB.boxNamePrimaryNodes, "primaryNodes"));
|
||||
test("wallets info", () => expect(DB.boxNameAllWalletsData, "wallets"));
|
||||
test("notifications",
|
||||
() => expect(DB.boxNameNotifications, "notificationModels"));
|
||||
test(
|
||||
"watched transactions",
|
||||
() => expect(
|
||||
DB.boxNameWatchedTransactions, "watchedTxNotificationModels"));
|
||||
test(
|
||||
"watched trades",
|
||||
() =>
|
||||
expect(DB.boxNameWatchedTrades, "watchedTradesNotificationModels"));
|
||||
test("trades", () => expect(DB.boxNameTrades, "exchangeTransactionsBox"));
|
||||
test("trade notes", () => expect(DB.boxNameTradeNotes, "tradeNotesBox"));
|
||||
test("tx <> trade lookup table",
|
||||
() => expect(DB.boxNameTradeLookup, "tradeToTxidLookUpBox"));
|
||||
test("favorite wallets",
|
||||
() => expect(DB.boxNameFavoriteWallets, "favoriteWallets"));
|
||||
test("preferences", () => expect(DB.boxNamePrefs, "prefs"));
|
||||
test(
|
||||
"deleted wallets to clear out on start",
|
||||
() =>
|
||||
expect(DB.boxNameWalletsToDeleteOnStart, "walletsToDeleteOnStart"));
|
||||
test("price cache",
|
||||
() => expect(DB.boxNamePriceCache, "priceAPIPrice24hCache"));
|
||||
|
||||
test("boxNameTxCache", () {
|
||||
for (final coin in Coin.values) {
|
||||
expect(DB.instance.boxNameTxCache(coin: coin), "${coin.name}_txCache");
|
||||
}
|
||||
});
|
||||
|
||||
test("boxNameSetCache", () {
|
||||
for (final coin in Coin.values) {
|
||||
expect(DB.instance.boxNameSetCache(coin: coin),
|
||||
"${coin.name}_anonymitySetCache");
|
||||
}
|
||||
});
|
||||
|
||||
test("boxNameUsedSerialsCache", () {
|
||||
for (final coin in Coin.values) {
|
||||
expect(DB.instance.boxNameUsedSerialsCache(coin: coin),
|
||||
"${coin.name}_usedSerialsCache");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group("tests requiring test hive environment", () {
|
||||
setUp(() async {
|
||||
await setUpTestHive();
|
||||
});
|
||||
|
||||
test("DB init", () async {});
|
||||
|
||||
tearDown(() async {
|
||||
await tearDownTestHive();
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/change_now_response.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/currency.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart';
|
||||
import 'package:stackwallet/models/exchange/estimated_rate_exchange_form_state.dart';
|
||||
import 'package:stackwallet/services/change_now/change_now.dart';
|
||||
|
||||
import 'estimated_rate_exchange_form_state_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([ChangeNow])
|
||||
void main() {
|
||||
final currencyA = Currency(
|
||||
ticker: "btc",
|
||||
name: "Bitcoin",
|
||||
image: "image.url",
|
||||
hasExternalId: false,
|
||||
isFiat: false,
|
||||
featured: false,
|
||||
isStable: true,
|
||||
supportsFixedRate: true,
|
||||
);
|
||||
final currencyB = Currency(
|
||||
ticker: "xmr",
|
||||
name: "Monero",
|
||||
image: "image.url",
|
||||
hasExternalId: false,
|
||||
isFiat: false,
|
||||
featured: false,
|
||||
isStable: true,
|
||||
supportsFixedRate: true,
|
||||
);
|
||||
final currencyC = Currency(
|
||||
ticker: "firo",
|
||||
name: "Firo",
|
||||
image: "image.url",
|
||||
hasExternalId: false,
|
||||
isFiat: false,
|
||||
featured: false,
|
||||
isStable: true,
|
||||
supportsFixedRate: true,
|
||||
);
|
||||
|
||||
test("EstimatedRateExchangeFormState constructor", () async {
|
||||
final state = EstimatedRateExchangeFormState();
|
||||
|
||||
expect(state.from, null);
|
||||
expect(state.to, null);
|
||||
expect(state.canExchange, false);
|
||||
expect(state.rate, null);
|
||||
expect(state.rateDisplayString, "N/A");
|
||||
expect(state.fromAmountString, "");
|
||||
expect(state.toAmountString, "");
|
||||
expect(state.minimumSendWarning, "");
|
||||
});
|
||||
|
||||
test("init EstimatedRateExchangeFormState", () async {
|
||||
final state = EstimatedRateExchangeFormState();
|
||||
|
||||
await state.init(currencyA, currencyB);
|
||||
|
||||
expect(state.from, currencyA);
|
||||
expect(state.to, currencyB);
|
||||
expect(state.canExchange, false);
|
||||
expect(state.rate, null);
|
||||
expect(state.rateDisplayString, "N/A");
|
||||
expect(state.fromAmountString, "");
|
||||
expect(state.toAmountString, "");
|
||||
expect(state.minimumSendWarning, "");
|
||||
});
|
||||
|
||||
test("updateTo on fresh state", () async {
|
||||
final state = EstimatedRateExchangeFormState();
|
||||
|
||||
await state.updateTo(currencyA, false);
|
||||
|
||||
expect(state.from, null);
|
||||
expect(state.to, currencyA);
|
||||
expect(state.canExchange, false);
|
||||
expect(state.rate, null);
|
||||
expect(state.rateDisplayString, "N/A");
|
||||
expect(state.fromAmountString, "");
|
||||
expect(state.toAmountString, "");
|
||||
expect(state.minimumSendWarning, "");
|
||||
});
|
||||
|
||||
test(
|
||||
"updateTo after updateFrom where amounts are null and getMinimalExchangeAmount succeeds",
|
||||
() async {
|
||||
final cn = MockChangeNow();
|
||||
|
||||
final state = EstimatedRateExchangeFormState();
|
||||
state.cnTesting = cn;
|
||||
|
||||
when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr"))
|
||||
.thenAnswer((_) async => ChangeNowResponse(value: Decimal.fromInt(42)));
|
||||
|
||||
await state.updateFrom(currencyA, true);
|
||||
await state.updateTo(currencyB, true);
|
||||
|
||||
expect(state.from, currencyA);
|
||||
expect(state.to, currencyB);
|
||||
expect(state.canExchange, false);
|
||||
expect(state.rate, null);
|
||||
expect(state.rateDisplayString, "N/A");
|
||||
expect(state.fromAmountString, "");
|
||||
expect(state.toAmountString, "");
|
||||
expect(state.minimumSendWarning, "");
|
||||
|
||||
verify(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr"))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
"updateTo after updateFrom where amounts are null and getMinimalExchangeAmount fails",
|
||||
() async {
|
||||
final cn = MockChangeNow();
|
||||
|
||||
final state = EstimatedRateExchangeFormState();
|
||||
state.cnTesting = cn;
|
||||
|
||||
when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr"))
|
||||
.thenAnswer((_) async => ChangeNowResponse());
|
||||
|
||||
await state.updateFrom(currencyA, true);
|
||||
await state.updateTo(currencyB, true);
|
||||
|
||||
expect(state.from, currencyA);
|
||||
expect(state.to, currencyB);
|
||||
expect(state.canExchange, false);
|
||||
expect(state.rate, null);
|
||||
expect(state.rateDisplayString, "N/A");
|
||||
expect(state.fromAmountString, "");
|
||||
expect(state.toAmountString, "");
|
||||
expect(state.minimumSendWarning, "");
|
||||
|
||||
verify(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr"))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
"updateTo after updateFrom and setFromAmountAndCalculateToAmount where fromAmount is less than the minimum required exchange amount",
|
||||
() async {
|
||||
final cn = MockChangeNow();
|
||||
|
||||
final state = EstimatedRateExchangeFormState();
|
||||
state.cnTesting = cn;
|
||||
|
||||
when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr"))
|
||||
.thenAnswer((_) async => ChangeNowResponse(value: Decimal.fromInt(42)));
|
||||
|
||||
await state.updateFrom(currencyA, true);
|
||||
await state.setFromAmountAndCalculateToAmount(Decimal.parse("10.10"), true);
|
||||
await state.updateTo(currencyB, true);
|
||||
|
||||
expect(state.from, currencyA);
|
||||
expect(state.to, currencyB);
|
||||
expect(state.canExchange, false);
|
||||
expect(state.rate, null);
|
||||
expect(state.rateDisplayString, "N/A");
|
||||
expect(state.fromAmountString, "10.10000000");
|
||||
expect(state.toAmountString, "");
|
||||
expect(state.minimumSendWarning, "Minimum amount 42 BTC");
|
||||
|
||||
verify(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr"))
|
||||
.called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
"updateTo after updateFrom and setFromAmountAndCalculateToAmount where fromAmount is greater than the minimum required exchange amount",
|
||||
() async {
|
||||
final cn = MockChangeNow();
|
||||
|
||||
final state = EstimatedRateExchangeFormState();
|
||||
state.cnTesting = cn;
|
||||
|
||||
when(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr"))
|
||||
.thenAnswer((_) async => ChangeNowResponse(value: Decimal.fromInt(42)));
|
||||
when(cn.getEstimatedExchangeAmount(
|
||||
fromTicker: "btc",
|
||||
toTicker: "xmr",
|
||||
fromAmount: Decimal.parse("110.10")))
|
||||
.thenAnswer((_) async => ChangeNowResponse(
|
||||
value: EstimatedExchangeAmount(
|
||||
transactionSpeedForecast: '10-60',
|
||||
rateId: 'some rate id',
|
||||
warningMessage: '',
|
||||
estimatedAmount: Decimal.parse("302.002348"),
|
||||
)));
|
||||
|
||||
await state.updateFrom(currencyA, true);
|
||||
await state.setFromAmountAndCalculateToAmount(
|
||||
Decimal.parse("110.10"), true);
|
||||
await state.updateTo(currencyB, true);
|
||||
|
||||
expect(state.from, currencyA);
|
||||
expect(state.to, currencyB);
|
||||
expect(state.canExchange, true);
|
||||
expect(state.rate, Decimal.parse("2.742982270663"));
|
||||
expect(state.rateDisplayString, "1 BTC ~2.74298227 XMR");
|
||||
expect(state.fromAmountString, "110.10000000");
|
||||
expect(state.toAmountString, "302.00234800");
|
||||
expect(state.minimumSendWarning, "");
|
||||
|
||||
verify(cn.getMinimalExchangeAmount(fromTicker: "btc", toTicker: "xmr"))
|
||||
.called(1);
|
||||
verify(cn.getEstimatedExchangeAmount(
|
||||
fromTicker: "btc",
|
||||
toTicker: "xmr",
|
||||
fromAmount: Decimal.parse("110.10")))
|
||||
.called(1);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
// Mocks generated by Mockito 5.2.0 from annotations
|
||||
// in stackwallet/test/models/exchange/estimated_rate_exchange_form_state_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
import 'dart:async' as _i5;
|
||||
|
||||
import 'package:decimal/decimal.dart' as _i7;
|
||||
import 'package:http/http.dart' as _i4;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'
|
||||
as _i12;
|
||||
import 'package:stackwallet/models/exchange/change_now/change_now_response.dart'
|
||||
as _i2;
|
||||
import 'package:stackwallet/models/exchange/change_now/currency.dart' as _i6;
|
||||
import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'
|
||||
as _i8;
|
||||
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'
|
||||
as _i10;
|
||||
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'
|
||||
as _i11;
|
||||
import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'
|
||||
as _i9;
|
||||
import 'package:stackwallet/services/change_now/change_now.dart' as _i3;
|
||||
|
||||
// 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
|
||||
|
||||
class _FakeChangeNowResponse_0<T> extends _i1.Fake
|
||||
implements _i2.ChangeNowResponse<T> {}
|
||||
|
||||
/// A class which mocks [ChangeNow].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockChangeNow extends _i1.Mock implements _i3.ChangeNow {
|
||||
MockChangeNow() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
set client(_i4.Client? _client) =>
|
||||
super.noSuchMethod(Invocation.setter(#client, _client),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
_i5.Future<_i2.ChangeNowResponse<List<_i6.Currency>>> getAvailableCurrencies(
|
||||
{bool? fixedRate, bool? active}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getAvailableCurrencies, [],
|
||||
{#fixedRate: fixedRate, #active: active}),
|
||||
returnValue: Future<_i2.ChangeNowResponse<List<_i6.Currency>>>.value(
|
||||
_FakeChangeNowResponse_0<List<_i6.Currency>>())) as _i5
|
||||
.Future<_i2.ChangeNowResponse<List<_i6.Currency>>>);
|
||||
@override
|
||||
_i5.Future<_i2.ChangeNowResponse<List<_i6.Currency>>> getPairedCurrencies(
|
||||
{String? ticker, bool? fixedRate}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getPairedCurrencies, [],
|
||||
{#ticker: ticker, #fixedRate: fixedRate}),
|
||||
returnValue: Future<_i2.ChangeNowResponse<List<_i6.Currency>>>.value(
|
||||
_FakeChangeNowResponse_0<List<_i6.Currency>>())) as _i5
|
||||
.Future<_i2.ChangeNowResponse<List<_i6.Currency>>>);
|
||||
@override
|
||||
_i5.Future<_i2.ChangeNowResponse<_i7.Decimal>> getMinimalExchangeAmount(
|
||||
{String? fromTicker, String? toTicker, String? apiKey}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getMinimalExchangeAmount, [], {
|
||||
#fromTicker: fromTicker,
|
||||
#toTicker: toTicker,
|
||||
#apiKey: apiKey
|
||||
}),
|
||||
returnValue: Future<_i2.ChangeNowResponse<_i7.Decimal>>.value(
|
||||
_FakeChangeNowResponse_0<_i7.Decimal>()))
|
||||
as _i5.Future<_i2.ChangeNowResponse<_i7.Decimal>>);
|
||||
@override
|
||||
_i5.Future<_i2.ChangeNowResponse<_i8.EstimatedExchangeAmount>>
|
||||
getEstimatedExchangeAmount(
|
||||
{String? fromTicker,
|
||||
String? toTicker,
|
||||
_i7.Decimal? fromAmount,
|
||||
String? apiKey}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getEstimatedExchangeAmount, [], {
|
||||
#fromTicker: fromTicker,
|
||||
#toTicker: toTicker,
|
||||
#fromAmount: fromAmount,
|
||||
#apiKey: apiKey
|
||||
}),
|
||||
returnValue: Future<
|
||||
_i2.ChangeNowResponse<
|
||||
_i8.EstimatedExchangeAmount>>.value(
|
||||
_FakeChangeNowResponse_0<_i8.EstimatedExchangeAmount>()))
|
||||
as _i5
|
||||
.Future<_i2.ChangeNowResponse<_i8.EstimatedExchangeAmount>>);
|
||||
@override
|
||||
_i5.Future<_i2.ChangeNowResponse<_i8.EstimatedExchangeAmount>>
|
||||
getEstimatedFixedRateExchangeAmount(
|
||||
{String? fromTicker,
|
||||
String? toTicker,
|
||||
_i7.Decimal? fromAmount,
|
||||
bool? useRateId = true,
|
||||
String? apiKey}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getEstimatedFixedRateExchangeAmount, [], {
|
||||
#fromTicker: fromTicker,
|
||||
#toTicker: toTicker,
|
||||
#fromAmount: fromAmount,
|
||||
#useRateId: useRateId,
|
||||
#apiKey: apiKey
|
||||
}),
|
||||
returnValue: Future<
|
||||
_i2.ChangeNowResponse<
|
||||
_i8.EstimatedExchangeAmount>>.value(
|
||||
_FakeChangeNowResponse_0<_i8.EstimatedExchangeAmount>()))
|
||||
as _i5
|
||||
.Future<_i2.ChangeNowResponse<_i8.EstimatedExchangeAmount>>);
|
||||
@override
|
||||
_i5.Future<_i2.ChangeNowResponse<List<_i9.FixedRateMarket>>>
|
||||
getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getAvailableFixedRateMarkets, [], {#apiKey: apiKey}),
|
||||
returnValue:
|
||||
Future<_i2.ChangeNowResponse<List<_i9.FixedRateMarket>>>.value(
|
||||
_FakeChangeNowResponse_0<List<_i9.FixedRateMarket>>())) as _i5
|
||||
.Future<_i2.ChangeNowResponse<List<_i9.FixedRateMarket>>>);
|
||||
@override
|
||||
_i5.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>
|
||||
createStandardExchangeTransaction(
|
||||
{String? fromTicker,
|
||||
String? toTicker,
|
||||
String? receivingAddress,
|
||||
_i7.Decimal? amount,
|
||||
String? extraId = r'',
|
||||
String? userId = r'',
|
||||
String? contactEmail = r'',
|
||||
String? refundAddress = r'',
|
||||
String? refundExtraId = r'',
|
||||
String? apiKey}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#createStandardExchangeTransaction, [], {
|
||||
#fromTicker: fromTicker,
|
||||
#toTicker: toTicker,
|
||||
#receivingAddress: receivingAddress,
|
||||
#amount: amount,
|
||||
#extraId: extraId,
|
||||
#userId: userId,
|
||||
#contactEmail: contactEmail,
|
||||
#refundAddress: refundAddress,
|
||||
#refundExtraId: refundExtraId,
|
||||
#apiKey: apiKey
|
||||
}),
|
||||
returnValue: Future<
|
||||
_i2.ChangeNowResponse<_i10.ExchangeTransaction>>.value(
|
||||
_FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) as _i5
|
||||
.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>);
|
||||
@override
|
||||
_i5.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>
|
||||
createFixedRateExchangeTransaction(
|
||||
{String? fromTicker,
|
||||
String? toTicker,
|
||||
String? receivingAddress,
|
||||
_i7.Decimal? amount,
|
||||
String? rateId,
|
||||
String? extraId = r'',
|
||||
String? userId = r'',
|
||||
String? contactEmail = r'',
|
||||
String? refundAddress = r'',
|
||||
String? refundExtraId = r'',
|
||||
String? apiKey}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#createFixedRateExchangeTransaction, [], {
|
||||
#fromTicker: fromTicker,
|
||||
#toTicker: toTicker,
|
||||
#receivingAddress: receivingAddress,
|
||||
#amount: amount,
|
||||
#rateId: rateId,
|
||||
#extraId: extraId,
|
||||
#userId: userId,
|
||||
#contactEmail: contactEmail,
|
||||
#refundAddress: refundAddress,
|
||||
#refundExtraId: refundExtraId,
|
||||
#apiKey: apiKey
|
||||
}),
|
||||
returnValue: Future<
|
||||
_i2.ChangeNowResponse<_i10.ExchangeTransaction>>.value(
|
||||
_FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) as _i5
|
||||
.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>);
|
||||
@override
|
||||
_i5.Future<_i2.ChangeNowResponse<_i11.ExchangeTransactionStatus>>
|
||||
getTransactionStatus({String? id, String? apiKey}) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getTransactionStatus, [], {#id: id, #apiKey: apiKey}),
|
||||
returnValue:
|
||||
Future<_i2.ChangeNowResponse<_i11.ExchangeTransactionStatus>>.value(
|
||||
_FakeChangeNowResponse_0<_i11.ExchangeTransactionStatus>())) as _i5
|
||||
.Future<_i2.ChangeNowResponse<_i11.ExchangeTransactionStatus>>);
|
||||
@override
|
||||
_i5.Future<_i2.ChangeNowResponse<List<_i12.AvailableFloatingRatePair>>>
|
||||
getAvailableFloatingRatePairs({bool? includePartners = false}) => (super
|
||||
.noSuchMethod(
|
||||
Invocation.method(#getAvailableFloatingRatePairs, [],
|
||||
{#includePartners: includePartners}),
|
||||
returnValue:
|
||||
Future<_i2.ChangeNowResponse<List<_i12.AvailableFloatingRatePair>>>.value(
|
||||
_FakeChangeNowResponse_0<List<_i12.AvailableFloatingRatePair>>())) as _i5
|
||||
.Future<_i2.ChangeNowResponse<List<_i12.AvailableFloatingRatePair>>>);
|
||||
}
|
15
test/models/isar/log_test.dart
Normal file
15
test/models/isar/log_test.dart
Normal file
|
@ -0,0 +1,15 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:stackwallet/models/isar/models/log.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
||||
void main() {
|
||||
test("Log class", () {
|
||||
final log = Log()
|
||||
..message = "hello"
|
||||
..timestampInMillisUTC = 100000001
|
||||
..logLevel = LogLevel.Fatal;
|
||||
|
||||
expect(log.toString(), "[Fatal][1970-01-02 03:46:40.001Z]: hello");
|
||||
expect(log.id, -9223372036854775808);
|
||||
});
|
||||
}
|
|
@ -2,20 +2,33 @@
|
|||
// in stackwallet/test/screen_tests/exchange/exchange_view_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
import 'dart:async' as _i6;
|
||||
import 'dart:ui' as _i7;
|
||||
import 'dart:async' as _i7;
|
||||
import 'dart:ui' as _i8;
|
||||
|
||||
import 'package:decimal/decimal.dart' as _i15;
|
||||
import 'package:http/http.dart' as _i13;
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:stackwallet/models/exchange/change_now/available_floating_rate_pair.dart'
|
||||
as _i19;
|
||||
import 'package:stackwallet/models/exchange/change_now/change_now_response.dart'
|
||||
as _i2;
|
||||
import 'package:stackwallet/models/exchange/change_now/currency.dart' as _i14;
|
||||
import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'
|
||||
as _i16;
|
||||
import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'
|
||||
as _i9;
|
||||
as _i10;
|
||||
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'
|
||||
as _i18;
|
||||
import 'package:stackwallet/models/exchange/change_now/fixed_rate_market.dart'
|
||||
as _i17;
|
||||
import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'
|
||||
as _i4;
|
||||
import 'package:stackwallet/services/change_now/change_now.dart' as _i11;
|
||||
import 'package:stackwallet/services/trade_notes_service.dart' as _i10;
|
||||
import 'package:stackwallet/services/trade_service.dart' as _i8;
|
||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i5;
|
||||
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i3;
|
||||
import 'package:stackwallet/utilities/prefs.dart' as _i2;
|
||||
as _i5;
|
||||
import 'package:stackwallet/services/change_now/change_now.dart' as _i12;
|
||||
import 'package:stackwallet/services/trade_notes_service.dart' as _i11;
|
||||
import 'package:stackwallet/services/trade_service.dart' as _i9;
|
||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i6;
|
||||
import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i4;
|
||||
import 'package:stackwallet/utilities/prefs.dart' as _i3;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
|
@ -27,10 +40,13 @@ import 'package:stackwallet/utilities/prefs.dart' as _i2;
|
|||
// ignore_for_file: unnecessary_parenthesis
|
||||
// ignore_for_file: camel_case_types
|
||||
|
||||
class _FakeChangeNowResponse_0<T> extends _i1.Fake
|
||||
implements _i2.ChangeNowResponse<T> {}
|
||||
|
||||
/// A class which mocks [Prefs].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockPrefs extends _i1.Mock implements _i2.Prefs {
|
||||
class MockPrefs extends _i1.Mock implements _i3.Prefs {
|
||||
MockPrefs() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
@ -69,11 +85,11 @@ class MockPrefs extends _i1.Mock implements _i2.Prefs {
|
|||
Invocation.setter(#walletIdsSyncOnStartup, walletIdsSyncOnStartup),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
_i3.SyncingType get syncType =>
|
||||
_i4.SyncingType get syncType =>
|
||||
(super.noSuchMethod(Invocation.getter(#syncType),
|
||||
returnValue: _i3.SyncingType.currentWalletOnly) as _i3.SyncingType);
|
||||
returnValue: _i4.SyncingType.currentWalletOnly) as _i4.SyncingType);
|
||||
@override
|
||||
set syncType(_i3.SyncingType? syncType) =>
|
||||
set syncType(_i4.SyncingType? syncType) =>
|
||||
super.noSuchMethod(Invocation.setter(#syncType, syncType),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
|
@ -109,11 +125,11 @@ class MockPrefs extends _i1.Mock implements _i2.Prefs {
|
|||
super.noSuchMethod(Invocation.setter(#currency, newCurrency),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
_i4.ExchangeRateType get exchangeRateType =>
|
||||
_i5.ExchangeRateType get exchangeRateType =>
|
||||
(super.noSuchMethod(Invocation.getter(#exchangeRateType),
|
||||
returnValue: _i4.ExchangeRateType.estimated) as _i4.ExchangeRateType);
|
||||
returnValue: _i5.ExchangeRateType.estimated) as _i5.ExchangeRateType);
|
||||
@override
|
||||
set exchangeRateType(_i4.ExchangeRateType? exchangeRateType) =>
|
||||
set exchangeRateType(_i5.ExchangeRateType? exchangeRateType) =>
|
||||
super.noSuchMethod(Invocation.setter(#exchangeRateType, exchangeRateType),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
|
@ -153,12 +169,12 @@ class MockPrefs extends _i1.Mock implements _i2.Prefs {
|
|||
Invocation.setter(#autoBackupLocation, autoBackupLocation),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
_i5.BackupFrequencyType get backupFrequencyType =>
|
||||
_i6.BackupFrequencyType get backupFrequencyType =>
|
||||
(super.noSuchMethod(Invocation.getter(#backupFrequencyType),
|
||||
returnValue: _i5.BackupFrequencyType.everyTenMinutes)
|
||||
as _i5.BackupFrequencyType);
|
||||
returnValue: _i6.BackupFrequencyType.everyTenMinutes)
|
||||
as _i6.BackupFrequencyType);
|
||||
@override
|
||||
set backupFrequencyType(_i5.BackupFrequencyType? backupFrequencyType) =>
|
||||
set backupFrequencyType(_i6.BackupFrequencyType? backupFrequencyType) =>
|
||||
super.noSuchMethod(
|
||||
Invocation.setter(#backupFrequencyType, backupFrequencyType),
|
||||
returnValueForMissingStub: null);
|
||||
|
@ -171,20 +187,20 @@ class MockPrefs extends _i1.Mock implements _i2.Prefs {
|
|||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
|
||||
as bool);
|
||||
@override
|
||||
_i6.Future<void> init() => (super.noSuchMethod(Invocation.method(#init, []),
|
||||
_i7.Future<void> init() => (super.noSuchMethod(Invocation.method(#init, []),
|
||||
returnValue: Future<void>.value(),
|
||||
returnValueForMissingStub: Future<void>.value()) as _i6.Future<void>);
|
||||
returnValueForMissingStub: Future<void>.value()) as _i7.Future<void>);
|
||||
@override
|
||||
_i6.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
|
||||
_i7.Future<void> incrementCurrentNotificationIndex() => (super.noSuchMethod(
|
||||
Invocation.method(#incrementCurrentNotificationIndex, []),
|
||||
returnValue: Future<void>.value(),
|
||||
returnValueForMissingStub: Future<void>.value()) as _i6.Future<void>);
|
||||
returnValueForMissingStub: Future<void>.value()) as _i7.Future<void>);
|
||||
@override
|
||||
void addListener(_i7.VoidCallback? listener) =>
|
||||
void addListener(_i8.VoidCallback? listener) =>
|
||||
super.noSuchMethod(Invocation.method(#addListener, [listener]),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
void removeListener(_i7.VoidCallback? listener) =>
|
||||
void removeListener(_i8.VoidCallback? listener) =>
|
||||
super.noSuchMethod(Invocation.method(#removeListener, [listener]),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
|
@ -199,57 +215,57 @@ class MockPrefs extends _i1.Mock implements _i2.Prefs {
|
|||
/// A class which mocks [TradesService].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockTradesService extends _i1.Mock implements _i8.TradesService {
|
||||
class MockTradesService extends _i1.Mock implements _i9.TradesService {
|
||||
MockTradesService() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
List<_i9.ExchangeTransaction> get trades =>
|
||||
List<_i10.ExchangeTransaction> get trades =>
|
||||
(super.noSuchMethod(Invocation.getter(#trades),
|
||||
returnValue: <_i9.ExchangeTransaction>[])
|
||||
as List<_i9.ExchangeTransaction>);
|
||||
returnValue: <_i10.ExchangeTransaction>[])
|
||||
as List<_i10.ExchangeTransaction>);
|
||||
@override
|
||||
bool get hasListeners =>
|
||||
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false)
|
||||
as bool);
|
||||
@override
|
||||
_i6.Future<void> add(
|
||||
{_i9.ExchangeTransaction? trade, bool? shouldNotifyListeners}) =>
|
||||
_i7.Future<void> add(
|
||||
{_i10.ExchangeTransaction? trade, bool? shouldNotifyListeners}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#add, [],
|
||||
{#trade: trade, #shouldNotifyListeners: shouldNotifyListeners}),
|
||||
returnValue: Future<void>.value(),
|
||||
returnValueForMissingStub: Future<void>.value()) as _i6.Future<void>);
|
||||
returnValueForMissingStub: Future<void>.value()) as _i7.Future<void>);
|
||||
@override
|
||||
_i6.Future<void> edit(
|
||||
{_i9.ExchangeTransaction? trade, bool? shouldNotifyListeners}) =>
|
||||
_i7.Future<void> edit(
|
||||
{_i10.ExchangeTransaction? trade, bool? shouldNotifyListeners}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#edit, [],
|
||||
{#trade: trade, #shouldNotifyListeners: shouldNotifyListeners}),
|
||||
returnValue: Future<void>.value(),
|
||||
returnValueForMissingStub: Future<void>.value()) as _i6.Future<void>);
|
||||
returnValueForMissingStub: Future<void>.value()) as _i7.Future<void>);
|
||||
@override
|
||||
_i6.Future<void> delete(
|
||||
{_i9.ExchangeTransaction? trade, bool? shouldNotifyListeners}) =>
|
||||
_i7.Future<void> delete(
|
||||
{_i10.ExchangeTransaction? trade, bool? shouldNotifyListeners}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#delete, [],
|
||||
{#trade: trade, #shouldNotifyListeners: shouldNotifyListeners}),
|
||||
returnValue: Future<void>.value(),
|
||||
returnValueForMissingStub: Future<void>.value()) as _i6.Future<void>);
|
||||
returnValueForMissingStub: Future<void>.value()) as _i7.Future<void>);
|
||||
@override
|
||||
_i6.Future<void> deleteByUuid({String? uuid, bool? shouldNotifyListeners}) =>
|
||||
_i7.Future<void> deleteByUuid({String? uuid, bool? shouldNotifyListeners}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#deleteByUuid, [],
|
||||
{#uuid: uuid, #shouldNotifyListeners: shouldNotifyListeners}),
|
||||
returnValue: Future<void>.value(),
|
||||
returnValueForMissingStub: Future<void>.value()) as _i6.Future<void>);
|
||||
returnValueForMissingStub: Future<void>.value()) as _i7.Future<void>);
|
||||
@override
|
||||
void addListener(_i7.VoidCallback? listener) =>
|
||||
void addListener(_i8.VoidCallback? listener) =>
|
||||
super.noSuchMethod(Invocation.method(#addListener, [listener]),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
void removeListener(_i7.VoidCallback? listener) =>
|
||||
void removeListener(_i8.VoidCallback? listener) =>
|
||||
super.noSuchMethod(Invocation.method(#removeListener, [listener]),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
|
@ -264,7 +280,7 @@ class MockTradesService extends _i1.Mock implements _i8.TradesService {
|
|||
/// A class which mocks [TradeNotesService].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockTradeNotesService extends _i1.Mock implements _i10.TradeNotesService {
|
||||
class MockTradeNotesService extends _i1.Mock implements _i11.TradeNotesService {
|
||||
MockTradeNotesService() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
@ -281,21 +297,21 @@ class MockTradeNotesService extends _i1.Mock implements _i10.TradeNotesService {
|
|||
(super.noSuchMethod(Invocation.method(#getNote, [], {#tradeId: tradeId}),
|
||||
returnValue: '') as String);
|
||||
@override
|
||||
_i6.Future<void> set({String? tradeId, String? note}) => (super.noSuchMethod(
|
||||
_i7.Future<void> set({String? tradeId, String? note}) => (super.noSuchMethod(
|
||||
Invocation.method(#set, [], {#tradeId: tradeId, #note: note}),
|
||||
returnValue: Future<void>.value(),
|
||||
returnValueForMissingStub: Future<void>.value()) as _i6.Future<void>);
|
||||
returnValueForMissingStub: Future<void>.value()) as _i7.Future<void>);
|
||||
@override
|
||||
_i6.Future<void> delete({String? tradeId}) =>
|
||||
_i7.Future<void> delete({String? tradeId}) =>
|
||||
(super.noSuchMethod(Invocation.method(#delete, [], {#tradeId: tradeId}),
|
||||
returnValue: Future<void>.value(),
|
||||
returnValueForMissingStub: Future<void>.value()) as _i6.Future<void>);
|
||||
returnValueForMissingStub: Future<void>.value()) as _i7.Future<void>);
|
||||
@override
|
||||
void addListener(_i7.VoidCallback? listener) =>
|
||||
void addListener(_i8.VoidCallback? listener) =>
|
||||
super.noSuchMethod(Invocation.method(#addListener, [listener]),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
void removeListener(_i7.VoidCallback? listener) =>
|
||||
void removeListener(_i8.VoidCallback? listener) =>
|
||||
super.noSuchMethod(Invocation.method(#removeListener, [listener]),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
|
@ -310,8 +326,175 @@ class MockTradeNotesService extends _i1.Mock implements _i10.TradeNotesService {
|
|||
/// A class which mocks [ChangeNow].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockChangeNow extends _i1.Mock implements _i11.ChangeNow {
|
||||
class MockChangeNow extends _i1.Mock implements _i12.ChangeNow {
|
||||
MockChangeNow() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
set client(_i13.Client? _client) =>
|
||||
super.noSuchMethod(Invocation.setter(#client, _client),
|
||||
returnValueForMissingStub: null);
|
||||
@override
|
||||
_i7.Future<_i2.ChangeNowResponse<List<_i14.Currency>>> getAvailableCurrencies(
|
||||
{bool? fixedRate, bool? active}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getAvailableCurrencies, [],
|
||||
{#fixedRate: fixedRate, #active: active}),
|
||||
returnValue: Future<_i2.ChangeNowResponse<List<_i14.Currency>>>.value(
|
||||
_FakeChangeNowResponse_0<List<_i14.Currency>>())) as _i7
|
||||
.Future<_i2.ChangeNowResponse<List<_i14.Currency>>>);
|
||||
@override
|
||||
_i7.Future<_i2.ChangeNowResponse<List<_i14.Currency>>> getPairedCurrencies(
|
||||
{String? ticker, bool? fixedRate}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getPairedCurrencies, [],
|
||||
{#ticker: ticker, #fixedRate: fixedRate}),
|
||||
returnValue: Future<_i2.ChangeNowResponse<List<_i14.Currency>>>.value(
|
||||
_FakeChangeNowResponse_0<List<_i14.Currency>>())) as _i7
|
||||
.Future<_i2.ChangeNowResponse<List<_i14.Currency>>>);
|
||||
@override
|
||||
_i7.Future<_i2.ChangeNowResponse<_i15.Decimal>> getMinimalExchangeAmount(
|
||||
{String? fromTicker, String? toTicker, String? apiKey}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getMinimalExchangeAmount, [], {
|
||||
#fromTicker: fromTicker,
|
||||
#toTicker: toTicker,
|
||||
#apiKey: apiKey
|
||||
}),
|
||||
returnValue: Future<_i2.ChangeNowResponse<_i15.Decimal>>.value(
|
||||
_FakeChangeNowResponse_0<_i15.Decimal>()))
|
||||
as _i7.Future<_i2.ChangeNowResponse<_i15.Decimal>>);
|
||||
@override
|
||||
_i7.Future<_i2.ChangeNowResponse<_i16.EstimatedExchangeAmount>>
|
||||
getEstimatedExchangeAmount(
|
||||
{String? fromTicker,
|
||||
String? toTicker,
|
||||
_i15.Decimal? fromAmount,
|
||||
String? apiKey}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getEstimatedExchangeAmount, [], {
|
||||
#fromTicker: fromTicker,
|
||||
#toTicker: toTicker,
|
||||
#fromAmount: fromAmount,
|
||||
#apiKey: apiKey
|
||||
}),
|
||||
returnValue: Future<
|
||||
_i2.ChangeNowResponse<
|
||||
_i16.EstimatedExchangeAmount>>.value(
|
||||
_FakeChangeNowResponse_0<_i16.EstimatedExchangeAmount>()))
|
||||
as _i7
|
||||
.Future<_i2.ChangeNowResponse<_i16.EstimatedExchangeAmount>>);
|
||||
@override
|
||||
_i7.Future<_i2.ChangeNowResponse<_i16.EstimatedExchangeAmount>>
|
||||
getEstimatedFixedRateExchangeAmount(
|
||||
{String? fromTicker,
|
||||
String? toTicker,
|
||||
_i15.Decimal? fromAmount,
|
||||
bool? useRateId = true,
|
||||
String? apiKey}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getEstimatedFixedRateExchangeAmount, [], {
|
||||
#fromTicker: fromTicker,
|
||||
#toTicker: toTicker,
|
||||
#fromAmount: fromAmount,
|
||||
#useRateId: useRateId,
|
||||
#apiKey: apiKey
|
||||
}),
|
||||
returnValue: Future<
|
||||
_i2.ChangeNowResponse<
|
||||
_i16.EstimatedExchangeAmount>>.value(
|
||||
_FakeChangeNowResponse_0<_i16.EstimatedExchangeAmount>()))
|
||||
as _i7
|
||||
.Future<_i2.ChangeNowResponse<_i16.EstimatedExchangeAmount>>);
|
||||
@override
|
||||
_i7.Future<_i2.ChangeNowResponse<List<_i17.FixedRateMarket>>>
|
||||
getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getAvailableFixedRateMarkets, [], {#apiKey: apiKey}),
|
||||
returnValue:
|
||||
Future<_i2.ChangeNowResponse<List<_i17.FixedRateMarket>>>.value(
|
||||
_FakeChangeNowResponse_0<List<_i17.FixedRateMarket>>())) as _i7
|
||||
.Future<_i2.ChangeNowResponse<List<_i17.FixedRateMarket>>>);
|
||||
@override
|
||||
_i7.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>
|
||||
createStandardExchangeTransaction(
|
||||
{String? fromTicker,
|
||||
String? toTicker,
|
||||
String? receivingAddress,
|
||||
_i15.Decimal? amount,
|
||||
String? extraId = r'',
|
||||
String? userId = r'',
|
||||
String? contactEmail = r'',
|
||||
String? refundAddress = r'',
|
||||
String? refundExtraId = r'',
|
||||
String? apiKey}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#createStandardExchangeTransaction, [], {
|
||||
#fromTicker: fromTicker,
|
||||
#toTicker: toTicker,
|
||||
#receivingAddress: receivingAddress,
|
||||
#amount: amount,
|
||||
#extraId: extraId,
|
||||
#userId: userId,
|
||||
#contactEmail: contactEmail,
|
||||
#refundAddress: refundAddress,
|
||||
#refundExtraId: refundExtraId,
|
||||
#apiKey: apiKey
|
||||
}),
|
||||
returnValue: Future<
|
||||
_i2.ChangeNowResponse<_i10.ExchangeTransaction>>.value(
|
||||
_FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) as _i7
|
||||
.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>);
|
||||
@override
|
||||
_i7.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>
|
||||
createFixedRateExchangeTransaction(
|
||||
{String? fromTicker,
|
||||
String? toTicker,
|
||||
String? receivingAddress,
|
||||
_i15.Decimal? amount,
|
||||
String? rateId,
|
||||
String? extraId = r'',
|
||||
String? userId = r'',
|
||||
String? contactEmail = r'',
|
||||
String? refundAddress = r'',
|
||||
String? refundExtraId = r'',
|
||||
String? apiKey}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#createFixedRateExchangeTransaction, [], {
|
||||
#fromTicker: fromTicker,
|
||||
#toTicker: toTicker,
|
||||
#receivingAddress: receivingAddress,
|
||||
#amount: amount,
|
||||
#rateId: rateId,
|
||||
#extraId: extraId,
|
||||
#userId: userId,
|
||||
#contactEmail: contactEmail,
|
||||
#refundAddress: refundAddress,
|
||||
#refundExtraId: refundExtraId,
|
||||
#apiKey: apiKey
|
||||
}),
|
||||
returnValue: Future<
|
||||
_i2.ChangeNowResponse<_i10.ExchangeTransaction>>.value(
|
||||
_FakeChangeNowResponse_0<_i10.ExchangeTransaction>())) as _i7
|
||||
.Future<_i2.ChangeNowResponse<_i10.ExchangeTransaction>>);
|
||||
@override
|
||||
_i7.Future<_i2.ChangeNowResponse<_i18.ExchangeTransactionStatus>>
|
||||
getTransactionStatus({String? id, String? apiKey}) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getTransactionStatus, [], {#id: id, #apiKey: apiKey}),
|
||||
returnValue:
|
||||
Future<_i2.ChangeNowResponse<_i18.ExchangeTransactionStatus>>.value(
|
||||
_FakeChangeNowResponse_0<_i18.ExchangeTransactionStatus>())) as _i7
|
||||
.Future<_i2.ChangeNowResponse<_i18.ExchangeTransactionStatus>>);
|
||||
@override
|
||||
_i7.Future<_i2.ChangeNowResponse<List<_i19.AvailableFloatingRatePair>>>
|
||||
getAvailableFloatingRatePairs({bool? includePartners = false}) => (super
|
||||
.noSuchMethod(
|
||||
Invocation.method(#getAvailableFloatingRatePairs, [],
|
||||
{#includePartners: includePartners}),
|
||||
returnValue:
|
||||
Future<_i2.ChangeNowResponse<List<_i19.AvailableFloatingRatePair>>>.value(
|
||||
_FakeChangeNowResponse_0<List<_i19.AvailableFloatingRatePair>>())) as _i7
|
||||
.Future<_i2.ChangeNowResponse<List<_i19.AvailableFloatingRatePair>>>);
|
||||
}
|
||||
|
|
|
@ -339,80 +339,80 @@ void main() {
|
|||
});
|
||||
});
|
||||
|
||||
group("getEstimatedFixedRateExchangeAmount", () {
|
||||
test("getEstimatedFixedRateExchangeAmount succeeds", () async {
|
||||
final client = MockClient();
|
||||
ChangeNow.instance.client = client;
|
||||
|
||||
when(client.get(
|
||||
Uri.parse(
|
||||
"https://api.ChangeNow.io/v1/exchange-amount/fixed-rate/10/xmr_btc?api_key=testAPIKEY&useRateId=true"),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
)).thenAnswer((realInvocation) async =>
|
||||
Response(jsonEncode(estFixedRateExchangeAmountJSON), 200));
|
||||
|
||||
final result =
|
||||
await ChangeNow.instance.getEstimatedFixedRateExchangeAmount(
|
||||
fromTicker: "xmr",
|
||||
toTicker: "btc",
|
||||
fromAmount: Decimal.fromInt(10),
|
||||
apiKey: "testAPIKEY",
|
||||
);
|
||||
|
||||
expect(result.exception, null);
|
||||
expect(result.value == null, false);
|
||||
expect(result.value.toString(),
|
||||
'EstimatedExchangeAmount: {estimatedAmount: 0.07271053, transactionSpeedForecast: 10-60, warningMessage: null, rateId: 1t2W5KBPqhycSJVYpaNZzYWLfMr0kSFe, networkFee: 0.00002408}');
|
||||
});
|
||||
|
||||
test(
|
||||
"getEstimatedFixedRateExchangeAmount fails with ChangeNowExceptionType.serializeResponseError",
|
||||
() async {
|
||||
final client = MockClient();
|
||||
ChangeNow.instance.client = client;
|
||||
|
||||
when(client.get(
|
||||
Uri.parse(
|
||||
"https://api.ChangeNow.io/v1/exchange-amount/fixed-rate/10/xmr_btc?api_key=testAPIKEY&useRateId=true"),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
)).thenAnswer((realInvocation) async => Response('{"error": 42}', 200));
|
||||
|
||||
final result =
|
||||
await ChangeNow.instance.getEstimatedFixedRateExchangeAmount(
|
||||
fromTicker: "xmr",
|
||||
toTicker: "btc",
|
||||
fromAmount: Decimal.fromInt(10),
|
||||
apiKey: "testAPIKEY",
|
||||
);
|
||||
|
||||
expect(result.exception!.type,
|
||||
ChangeNowExceptionType.serializeResponseError);
|
||||
expect(result.value == null, true);
|
||||
});
|
||||
|
||||
test("getEstimatedFixedRateExchangeAmount fails for any other reason",
|
||||
() async {
|
||||
final client = MockClient();
|
||||
ChangeNow.instance.client = client;
|
||||
|
||||
when(client.get(
|
||||
Uri.parse(
|
||||
"https://api.ChangeNow.io/v1/exchange-amount/fixed-rate/10/xmr_btc?api_key=testAPIKEY&useRateId=true"),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
)).thenAnswer((realInvocation) async => Response('', 400));
|
||||
|
||||
final result =
|
||||
await ChangeNow.instance.getEstimatedFixedRateExchangeAmount(
|
||||
fromTicker: "xmr",
|
||||
toTicker: "btc",
|
||||
fromAmount: Decimal.fromInt(10),
|
||||
apiKey: "testAPIKEY",
|
||||
);
|
||||
|
||||
expect(result.exception!.type, ChangeNowExceptionType.generic);
|
||||
expect(result.value == null, true);
|
||||
});
|
||||
});
|
||||
// group("getEstimatedFixedRateExchangeAmount", () {
|
||||
// test("getEstimatedFixedRateExchangeAmount succeeds", () async {
|
||||
// final client = MockClient();
|
||||
// ChangeNow.instance.client = client;
|
||||
//
|
||||
// when(client.get(
|
||||
// Uri.parse(
|
||||
// "https://api.ChangeNow.io/v1/exchange-amount/fixed-rate/10/xmr_btc?api_key=testAPIKEY&useRateId=true"),
|
||||
// headers: {'Content-Type': 'application/json'},
|
||||
// )).thenAnswer((realInvocation) async =>
|
||||
// Response(jsonEncode(estFixedRateExchangeAmountJSON), 200));
|
||||
//
|
||||
// final result =
|
||||
// await ChangeNow.instance.getEstimatedFixedRateExchangeAmount(
|
||||
// fromTicker: "xmr",
|
||||
// toTicker: "btc",
|
||||
// fromAmount: Decimal.fromInt(10),
|
||||
// apiKey: "testAPIKEY",
|
||||
// );
|
||||
//
|
||||
// expect(result.exception, null);
|
||||
// expect(result.value == null, false);
|
||||
// expect(result.value.toString(),
|
||||
// 'EstimatedExchangeAmount: {estimatedAmount: 0.07271053, transactionSpeedForecast: 10-60, warningMessage: null, rateId: 1t2W5KBPqhycSJVYpaNZzYWLfMr0kSFe, networkFee: 0.00002408}');
|
||||
// });
|
||||
//
|
||||
// test(
|
||||
// "getEstimatedFixedRateExchangeAmount fails with ChangeNowExceptionType.serializeResponseError",
|
||||
// () async {
|
||||
// final client = MockClient();
|
||||
// ChangeNow.instance.client = client;
|
||||
//
|
||||
// when(client.get(
|
||||
// Uri.parse(
|
||||
// "https://api.ChangeNow.io/v1/exchange-amount/fixed-rate/10/xmr_btc?api_key=testAPIKEY&useRateId=true"),
|
||||
// headers: {'Content-Type': 'application/json'},
|
||||
// )).thenAnswer((realInvocation) async => Response('{"error": 42}', 200));
|
||||
//
|
||||
// final result =
|
||||
// await ChangeNow.instance.getEstimatedFixedRateExchangeAmount(
|
||||
// fromTicker: "xmr",
|
||||
// toTicker: "btc",
|
||||
// fromAmount: Decimal.fromInt(10),
|
||||
// apiKey: "testAPIKEY",
|
||||
// );
|
||||
//
|
||||
// expect(result.exception!.type,
|
||||
// ChangeNowExceptionType.serializeResponseError);
|
||||
// expect(result.value == null, true);
|
||||
// });
|
||||
//
|
||||
// test("getEstimatedFixedRateExchangeAmount fails for any other reason",
|
||||
// () async {
|
||||
// final client = MockClient();
|
||||
// ChangeNow.instance.client = client;
|
||||
//
|
||||
// when(client.get(
|
||||
// Uri.parse(
|
||||
// "https://api.ChangeNow.io/v1/exchange-amount/fixed-rate/10/xmr_btc?api_key=testAPIKEY&useRateId=true"),
|
||||
// headers: {'Content-Type': 'application/json'},
|
||||
// )).thenAnswer((realInvocation) async => Response('', 400));
|
||||
//
|
||||
// final result =
|
||||
// await ChangeNow.instance.getEstimatedFixedRateExchangeAmount(
|
||||
// fromTicker: "xmr",
|
||||
// toTicker: "btc",
|
||||
// fromAmount: Decimal.fromInt(10),
|
||||
// apiKey: "testAPIKEY",
|
||||
// );
|
||||
//
|
||||
// expect(result.exception!.type, ChangeNowExceptionType.generic);
|
||||
// expect(result.value == null, true);
|
||||
// });
|
||||
// });
|
||||
|
||||
group("getAvailableFixedRateMarkets", () {
|
||||
test("getAvailableFixedRateMarkets succeeds", () async {
|
||||
|
|
|
@ -30,7 +30,7 @@ void main() {
|
|||
expect(MINIMUM_CONFIRMATIONS, 2);
|
||||
});
|
||||
test("bitcoin dust limit", () async {
|
||||
expect(DUST_LIMIT, 546);
|
||||
expect(DUST_LIMIT, 294);
|
||||
});
|
||||
test("bitcoin mainnet genesis block hash", () async {
|
||||
expect(GENESIS_HASH_MAINNET,
|
||||
|
|
|
@ -176,4 +176,10 @@ class FakeCoinServiceAPI extends CoinServiceAPI {
|
|||
// TODO: implement testNetworkConnection
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> generateNewAddress() {
|
||||
// TODO: implement generateNewAddress
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ import 'firo_wallet_test.mocks.dart';
|
|||
import 'firo_wallet_test_parameters.dart';
|
||||
import 'sample_data/get_anonymity_set_sample_data.dart';
|
||||
import 'sample_data/get_used_serials_sample_data.dart';
|
||||
import 'sample_data/get_utxos_sample_data.dart';
|
||||
import 'sample_data/gethistory_samples.dart';
|
||||
import 'sample_data/transaction_data_samples.dart';
|
||||
|
||||
|
@ -3398,224 +3397,224 @@ void main() {
|
|||
await firo.exit();
|
||||
});
|
||||
|
||||
test("autoMint", () async {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
const MethodChannel('uk.spiralarm.flutter/devicelocale')
|
||||
.setMockMethodCallHandler((methodCall) async => 'en_US');
|
||||
|
||||
final client = MockElectrumX();
|
||||
final cachedClient = MockCachedElectrumX();
|
||||
final secureStore = FakeSecureStorage();
|
||||
final priceAPI = MockPriceAPI();
|
||||
|
||||
// mock electrumx client calls
|
||||
when(client.getServerFeatures()).thenAnswer((_) async => {
|
||||
"hosts": {},
|
||||
"pruning": null,
|
||||
"server_version": "Unit tests",
|
||||
"protocol_min": "1.4",
|
||||
"protocol_max": "1.4.2",
|
||||
"genesis_hash": GENESIS_HASH_MAINNET,
|
||||
"hash_function": "sha256",
|
||||
"services": []
|
||||
});
|
||||
|
||||
when(client.getBlockHeadTip()).thenAnswer(
|
||||
(_) async => {"height": 465873, "hex": "this value not used here"});
|
||||
|
||||
when(client.broadcastTransaction(rawTx: anyNamed("rawTx")))
|
||||
.thenAnswer((realInvocation) async {
|
||||
final rawTx =
|
||||
realInvocation.namedArguments[const Symbol("rawTx")] as String;
|
||||
final rawTxData = Format.stringToUint8List(rawTx);
|
||||
|
||||
final hash = sha256
|
||||
.convert(sha256.convert(rawTxData.toList(growable: false)).bytes);
|
||||
|
||||
final reversedBytes =
|
||||
Uint8List.fromList(hash.bytes.reversed.toList(growable: false));
|
||||
|
||||
final txid = Format.uint8listToString(reversedBytes);
|
||||
|
||||
return txid;
|
||||
});
|
||||
|
||||
when(client.estimateFee(blocks: 1))
|
||||
.thenAnswer((_) async => Decimal.parse("0.00001000"));
|
||||
when(client.estimateFee(blocks: 5))
|
||||
.thenAnswer((_) async => Decimal.parse("0.00001000"));
|
||||
when(client.estimateFee(blocks: 20))
|
||||
.thenAnswer((_) async => Decimal.parse("0.00001000"));
|
||||
|
||||
when(cachedClient.getAnonymitySet(
|
||||
groupId: "1", blockhash: "", coin: Coin.firo))
|
||||
.thenAnswer((_) async => GetAnonymitySetSampleData.data);
|
||||
when(cachedClient.getUsedCoinSerials(startNumber: 0, coin: Coin.firo))
|
||||
.thenAnswer(
|
||||
(_) async => GetUsedSerialsSampleData.serials['serials'] as List);
|
||||
|
||||
when(client.getLatestCoinId()).thenAnswer((_) async => 1);
|
||||
// when(client.getCoinsForRecovery(setId: 1))
|
||||
// .thenAnswer((_) async => getCoinsForRecoveryResponse);
|
||||
when(client.getUsedCoinSerials(startNumber: 0))
|
||||
.thenAnswer((_) async => GetUsedSerialsSampleData.serials);
|
||||
|
||||
// mock price calls
|
||||
when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer(
|
||||
(_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)});
|
||||
|
||||
// mock transaction calls
|
||||
when(cachedClient.getTransaction(
|
||||
txHash: SampleGetTransactionData.txHash0,
|
||||
coin: Coin.firo,
|
||||
)).thenAnswer((_) async => SampleGetTransactionData.txData0);
|
||||
when(cachedClient.getTransaction(
|
||||
txHash: SampleGetTransactionData.txHash1,
|
||||
coin: Coin.firo,
|
||||
)).thenAnswer((_) async => SampleGetTransactionData.txData1);
|
||||
when(cachedClient.getTransaction(
|
||||
txHash: SampleGetTransactionData.txHash2,
|
||||
coin: Coin.firo,
|
||||
)).thenAnswer((_) async => SampleGetTransactionData.txData2);
|
||||
when(cachedClient.getTransaction(
|
||||
txHash: SampleGetTransactionData.txHash3,
|
||||
coin: Coin.firo,
|
||||
)).thenAnswer((_) async => SampleGetTransactionData.txData3);
|
||||
when(cachedClient.getTransaction(
|
||||
txHash: SampleGetTransactionData.txHash4,
|
||||
coin: Coin.firo,
|
||||
)).thenAnswer((_) async => SampleGetTransactionData.txData4);
|
||||
when(cachedClient.getTransaction(
|
||||
txHash: SampleGetTransactionData.txHash5,
|
||||
coin: Coin.firo,
|
||||
)).thenAnswer((_) async => SampleGetTransactionData.txData5);
|
||||
when(cachedClient.getTransaction(
|
||||
txHash: SampleGetTransactionData.txHash6,
|
||||
coin: Coin.firo,
|
||||
)).thenAnswer((_) async => SampleGetTransactionData.txData6);
|
||||
when(cachedClient.getTransaction(
|
||||
txHash: SampleGetTransactionData.txHash7,
|
||||
coin: Coin.firo,
|
||||
)).thenAnswer((_) async => SampleGetTransactionData.txData7);
|
||||
when(cachedClient.getTransaction(
|
||||
txHash: SampleGetTransactionData.txHash8,
|
||||
coin: Coin.firo,
|
||||
)).thenAnswer((_) async => SampleGetTransactionData.txData8);
|
||||
when(cachedClient.getTransaction(
|
||||
txHash: SampleGetTransactionData.txHash9,
|
||||
coin: Coin.firo,
|
||||
)).thenAnswer((_) async => SampleGetTransactionData.txData9);
|
||||
when(cachedClient.getTransaction(
|
||||
txHash: SampleGetTransactionData.txHash10,
|
||||
coin: Coin.firo,
|
||||
)).thenAnswer((_) async => SampleGetTransactionData.txData10);
|
||||
when(cachedClient.getTransaction(
|
||||
txHash: SampleGetTransactionData.txHash11,
|
||||
coin: Coin.firo,
|
||||
)).thenAnswer((_) async => SampleGetTransactionData.txData11);
|
||||
when(cachedClient.getTransaction(
|
||||
txHash: SampleGetTransactionData.txHash12,
|
||||
coin: Coin.firo,
|
||||
)).thenAnswer((_) async => SampleGetTransactionData.txData12);
|
||||
|
||||
final firo = FiroWallet(
|
||||
walletName: testWalletName,
|
||||
walletId: "${testWalletId}autoMint",
|
||||
coin: Coin.firo,
|
||||
client: client,
|
||||
cachedClient: cachedClient,
|
||||
secureStore: secureStore,
|
||||
priceAPI: priceAPI,
|
||||
tracker: MockTransactionNotificationTracker(),
|
||||
);
|
||||
|
||||
// pre grab derivations in order to set up mock calls needed later on
|
||||
await firo.fillAddresses(TEST_MNEMONIC);
|
||||
final wallet = await Hive.openBox<dynamic>("${testWalletId}autoMint");
|
||||
await wallet.put(
|
||||
'receivingAddresses', RefreshTestParams.receivingAddresses);
|
||||
await wallet.put('changeAddresses', RefreshTestParams.changeAddresses);
|
||||
|
||||
final rcv = await secureStore.read(
|
||||
key: "${testWalletId}autoMint_receiveDerivations");
|
||||
final chg = await secureStore.read(
|
||||
key: "${testWalletId}autoMint_changeDerivations");
|
||||
final receiveDerivations =
|
||||
Map<String, dynamic>.from(jsonDecode(rcv as String) as Map);
|
||||
final changeDerivations =
|
||||
Map<String, dynamic>.from(jsonDecode(chg as String) as Map);
|
||||
|
||||
for (int i = 0; i < receiveDerivations.length; i++) {
|
||||
final receiveHash = AddressUtils.convertToScriptHash(
|
||||
receiveDerivations["$i"]!["address"] as String, firoNetwork);
|
||||
final changeHash = AddressUtils.convertToScriptHash(
|
||||
changeDerivations["$i"]!["address"] as String, firoNetwork);
|
||||
List<Map<String, dynamic>> data;
|
||||
switch (receiveHash) {
|
||||
case SampleGetHistoryData.scripthash0:
|
||||
data = SampleGetHistoryData.data0;
|
||||
break;
|
||||
case SampleGetHistoryData.scripthash1:
|
||||
data = SampleGetHistoryData.data1;
|
||||
break;
|
||||
case SampleGetHistoryData.scripthash2:
|
||||
data = SampleGetHistoryData.data2;
|
||||
break;
|
||||
case SampleGetHistoryData.scripthash3:
|
||||
data = SampleGetHistoryData.data3;
|
||||
break;
|
||||
default:
|
||||
data = [];
|
||||
}
|
||||
when(client.getHistory(scripthash: receiveHash))
|
||||
.thenAnswer((_) async => data);
|
||||
|
||||
switch (changeHash) {
|
||||
case SampleGetHistoryData.scripthash0:
|
||||
data = SampleGetHistoryData.data0;
|
||||
break;
|
||||
case SampleGetHistoryData.scripthash1:
|
||||
data = SampleGetHistoryData.data1;
|
||||
break;
|
||||
case SampleGetHistoryData.scripthash2:
|
||||
data = SampleGetHistoryData.data2;
|
||||
break;
|
||||
case SampleGetHistoryData.scripthash3:
|
||||
data = SampleGetHistoryData.data3;
|
||||
break;
|
||||
default:
|
||||
data = [];
|
||||
}
|
||||
|
||||
when(client.getHistory(scripthash: changeHash))
|
||||
.thenAnswer((_) async => data);
|
||||
}
|
||||
|
||||
when(client.getUTXOs(scripthash: GetUtxoSampleData.scriptHash0))
|
||||
.thenAnswer((_) async => GetUtxoSampleData.utxos0);
|
||||
when(client.getUTXOs(scripthash: GetUtxoSampleData.scriptHash1))
|
||||
.thenAnswer((_) async => GetUtxoSampleData.utxos1);
|
||||
|
||||
await firo.recoverFromMnemonic(
|
||||
mnemonic: TEST_MNEMONIC,
|
||||
maxUnusedAddressGap: 20,
|
||||
height: 0,
|
||||
maxNumberOfIndexesToCheck: 1000);
|
||||
|
||||
firo.timer = Timer(const Duration(minutes: 3), () {});
|
||||
|
||||
await firo.refresh();
|
||||
|
||||
bool flag = false;
|
||||
try {
|
||||
await firo.autoMint();
|
||||
} catch (_) {
|
||||
flag = true;
|
||||
}
|
||||
expect(flag, false);
|
||||
|
||||
await firo.exit();
|
||||
}, timeout: const Timeout(Duration(minutes: 3)));
|
||||
// test("autoMint", () async {
|
||||
// TestWidgetsFlutterBinding.ensureInitialized();
|
||||
// const MethodChannel('uk.spiralarm.flutter/devicelocale')
|
||||
// .setMockMethodCallHandler((methodCall) async => 'en_US');
|
||||
//
|
||||
// final client = MockElectrumX();
|
||||
// final cachedClient = MockCachedElectrumX();
|
||||
// final secureStore = FakeSecureStorage();
|
||||
// final priceAPI = MockPriceAPI();
|
||||
//
|
||||
// // mock electrumx client calls
|
||||
// when(client.getServerFeatures()).thenAnswer((_) async => {
|
||||
// "hosts": {},
|
||||
// "pruning": null,
|
||||
// "server_version": "Unit tests",
|
||||
// "protocol_min": "1.4",
|
||||
// "protocol_max": "1.4.2",
|
||||
// "genesis_hash": GENESIS_HASH_MAINNET,
|
||||
// "hash_function": "sha256",
|
||||
// "services": []
|
||||
// });
|
||||
//
|
||||
// when(client.getBlockHeadTip()).thenAnswer(
|
||||
// (_) async => {"height": 465873, "hex": "this value not used here"});
|
||||
//
|
||||
// when(client.broadcastTransaction(rawTx: anyNamed("rawTx")))
|
||||
// .thenAnswer((realInvocation) async {
|
||||
// final rawTx =
|
||||
// realInvocation.namedArguments[const Symbol("rawTx")] as String;
|
||||
// final rawTxData = Format.stringToUint8List(rawTx);
|
||||
//
|
||||
// final hash = sha256
|
||||
// .convert(sha256.convert(rawTxData.toList(growable: false)).bytes);
|
||||
//
|
||||
// final reversedBytes =
|
||||
// Uint8List.fromList(hash.bytes.reversed.toList(growable: false));
|
||||
//
|
||||
// final txid = Format.uint8listToString(reversedBytes);
|
||||
//
|
||||
// return txid;
|
||||
// });
|
||||
//
|
||||
// when(client.estimateFee(blocks: 1))
|
||||
// .thenAnswer((_) async => Decimal.parse("0.00001000"));
|
||||
// when(client.estimateFee(blocks: 5))
|
||||
// .thenAnswer((_) async => Decimal.parse("0.00001000"));
|
||||
// when(client.estimateFee(blocks: 20))
|
||||
// .thenAnswer((_) async => Decimal.parse("0.00001000"));
|
||||
//
|
||||
// when(cachedClient.getAnonymitySet(
|
||||
// groupId: "1", blockhash: "", coin: Coin.firo))
|
||||
// .thenAnswer((_) async => GetAnonymitySetSampleData.data);
|
||||
// when(cachedClient.getUsedCoinSerials(startNumber: 0, coin: Coin.firo))
|
||||
// .thenAnswer(
|
||||
// (_) async => GetUsedSerialsSampleData.serials['serials'] as List);
|
||||
//
|
||||
// when(client.getLatestCoinId()).thenAnswer((_) async => 1);
|
||||
// // when(client.getCoinsForRecovery(setId: 1))
|
||||
// // .thenAnswer((_) async => getCoinsForRecoveryResponse);
|
||||
// when(client.getUsedCoinSerials(startNumber: 0))
|
||||
// .thenAnswer((_) async => GetUsedSerialsSampleData.serials);
|
||||
//
|
||||
// // mock price calls
|
||||
// when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer(
|
||||
// (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)});
|
||||
//
|
||||
// // mock transaction calls
|
||||
// when(cachedClient.getTransaction(
|
||||
// txHash: SampleGetTransactionData.txHash0,
|
||||
// coin: Coin.firo,
|
||||
// )).thenAnswer((_) async => SampleGetTransactionData.txData0);
|
||||
// when(cachedClient.getTransaction(
|
||||
// txHash: SampleGetTransactionData.txHash1,
|
||||
// coin: Coin.firo,
|
||||
// )).thenAnswer((_) async => SampleGetTransactionData.txData1);
|
||||
// when(cachedClient.getTransaction(
|
||||
// txHash: SampleGetTransactionData.txHash2,
|
||||
// coin: Coin.firo,
|
||||
// )).thenAnswer((_) async => SampleGetTransactionData.txData2);
|
||||
// when(cachedClient.getTransaction(
|
||||
// txHash: SampleGetTransactionData.txHash3,
|
||||
// coin: Coin.firo,
|
||||
// )).thenAnswer((_) async => SampleGetTransactionData.txData3);
|
||||
// when(cachedClient.getTransaction(
|
||||
// txHash: SampleGetTransactionData.txHash4,
|
||||
// coin: Coin.firo,
|
||||
// )).thenAnswer((_) async => SampleGetTransactionData.txData4);
|
||||
// when(cachedClient.getTransaction(
|
||||
// txHash: SampleGetTransactionData.txHash5,
|
||||
// coin: Coin.firo,
|
||||
// )).thenAnswer((_) async => SampleGetTransactionData.txData5);
|
||||
// when(cachedClient.getTransaction(
|
||||
// txHash: SampleGetTransactionData.txHash6,
|
||||
// coin: Coin.firo,
|
||||
// )).thenAnswer((_) async => SampleGetTransactionData.txData6);
|
||||
// when(cachedClient.getTransaction(
|
||||
// txHash: SampleGetTransactionData.txHash7,
|
||||
// coin: Coin.firo,
|
||||
// )).thenAnswer((_) async => SampleGetTransactionData.txData7);
|
||||
// when(cachedClient.getTransaction(
|
||||
// txHash: SampleGetTransactionData.txHash8,
|
||||
// coin: Coin.firo,
|
||||
// )).thenAnswer((_) async => SampleGetTransactionData.txData8);
|
||||
// when(cachedClient.getTransaction(
|
||||
// txHash: SampleGetTransactionData.txHash9,
|
||||
// coin: Coin.firo,
|
||||
// )).thenAnswer((_) async => SampleGetTransactionData.txData9);
|
||||
// when(cachedClient.getTransaction(
|
||||
// txHash: SampleGetTransactionData.txHash10,
|
||||
// coin: Coin.firo,
|
||||
// )).thenAnswer((_) async => SampleGetTransactionData.txData10);
|
||||
// when(cachedClient.getTransaction(
|
||||
// txHash: SampleGetTransactionData.txHash11,
|
||||
// coin: Coin.firo,
|
||||
// )).thenAnswer((_) async => SampleGetTransactionData.txData11);
|
||||
// when(cachedClient.getTransaction(
|
||||
// txHash: SampleGetTransactionData.txHash12,
|
||||
// coin: Coin.firo,
|
||||
// )).thenAnswer((_) async => SampleGetTransactionData.txData12);
|
||||
//
|
||||
// final firo = FiroWallet(
|
||||
// walletName: testWalletName,
|
||||
// walletId: "${testWalletId}autoMint",
|
||||
// coin: Coin.firo,
|
||||
// client: client,
|
||||
// cachedClient: cachedClient,
|
||||
// secureStore: secureStore,
|
||||
// priceAPI: priceAPI,
|
||||
// tracker: MockTransactionNotificationTracker(),
|
||||
// );
|
||||
//
|
||||
// // pre grab derivations in order to set up mock calls needed later on
|
||||
// await firo.fillAddresses(TEST_MNEMONIC);
|
||||
// final wallet = await Hive.openBox<dynamic>("${testWalletId}autoMint");
|
||||
// await wallet.put(
|
||||
// 'receivingAddresses', RefreshTestParams.receivingAddresses);
|
||||
// await wallet.put('changeAddresses', RefreshTestParams.changeAddresses);
|
||||
//
|
||||
// final rcv = await secureStore.read(
|
||||
// key: "${testWalletId}autoMint_receiveDerivations");
|
||||
// final chg = await secureStore.read(
|
||||
// key: "${testWalletId}autoMint_changeDerivations");
|
||||
// final receiveDerivations =
|
||||
// Map<String, dynamic>.from(jsonDecode(rcv as String) as Map);
|
||||
// final changeDerivations =
|
||||
// Map<String, dynamic>.from(jsonDecode(chg as String) as Map);
|
||||
//
|
||||
// for (int i = 0; i < receiveDerivations.length; i++) {
|
||||
// final receiveHash = AddressUtils.convertToScriptHash(
|
||||
// receiveDerivations["$i"]!["address"] as String, firoNetwork);
|
||||
// final changeHash = AddressUtils.convertToScriptHash(
|
||||
// changeDerivations["$i"]!["address"] as String, firoNetwork);
|
||||
// List<Map<String, dynamic>> data;
|
||||
// switch (receiveHash) {
|
||||
// case SampleGetHistoryData.scripthash0:
|
||||
// data = SampleGetHistoryData.data0;
|
||||
// break;
|
||||
// case SampleGetHistoryData.scripthash1:
|
||||
// data = SampleGetHistoryData.data1;
|
||||
// break;
|
||||
// case SampleGetHistoryData.scripthash2:
|
||||
// data = SampleGetHistoryData.data2;
|
||||
// break;
|
||||
// case SampleGetHistoryData.scripthash3:
|
||||
// data = SampleGetHistoryData.data3;
|
||||
// break;
|
||||
// default:
|
||||
// data = [];
|
||||
// }
|
||||
// when(client.getHistory(scripthash: receiveHash))
|
||||
// .thenAnswer((_) async => data);
|
||||
//
|
||||
// switch (changeHash) {
|
||||
// case SampleGetHistoryData.scripthash0:
|
||||
// data = SampleGetHistoryData.data0;
|
||||
// break;
|
||||
// case SampleGetHistoryData.scripthash1:
|
||||
// data = SampleGetHistoryData.data1;
|
||||
// break;
|
||||
// case SampleGetHistoryData.scripthash2:
|
||||
// data = SampleGetHistoryData.data2;
|
||||
// break;
|
||||
// case SampleGetHistoryData.scripthash3:
|
||||
// data = SampleGetHistoryData.data3;
|
||||
// break;
|
||||
// default:
|
||||
// data = [];
|
||||
// }
|
||||
//
|
||||
// when(client.getHistory(scripthash: changeHash))
|
||||
// .thenAnswer((_) async => data);
|
||||
// }
|
||||
//
|
||||
// when(client.getUTXOs(scripthash: GetUtxoSampleData.scriptHash0))
|
||||
// .thenAnswer((_) async => GetUtxoSampleData.utxos0);
|
||||
// when(client.getUTXOs(scripthash: GetUtxoSampleData.scriptHash1))
|
||||
// .thenAnswer((_) async => GetUtxoSampleData.utxos1);
|
||||
//
|
||||
// await firo.recoverFromMnemonic(
|
||||
// mnemonic: TEST_MNEMONIC,
|
||||
// maxUnusedAddressGap: 20,
|
||||
// height: 0,
|
||||
// maxNumberOfIndexesToCheck: 1000);
|
||||
//
|
||||
// firo.timer = Timer(const Duration(minutes: 3), () {});
|
||||
//
|
||||
// await firo.refresh();
|
||||
//
|
||||
// bool flag = false;
|
||||
// try {
|
||||
// await firo.autoMint();
|
||||
// } catch (_) {
|
||||
// flag = true;
|
||||
// }
|
||||
// expect(flag, false);
|
||||
//
|
||||
// await firo.exit();
|
||||
// }, timeout: const Timeout(Duration(minutes: 3)));
|
||||
|
||||
test("exit", () async {
|
||||
final firo = FiroWallet(
|
||||
|
|
|
@ -386,11 +386,11 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet {
|
|||
returnValue: Future<void>.value(),
|
||||
returnValueForMissingStub: Future<void>.value()) as _i8.Future<void>);
|
||||
@override
|
||||
_i8.Future<dynamic> GetCoinsToJoinSplit(int? required) =>
|
||||
_i8.Future<dynamic> getCoinsToJoinSplit(int? required) =>
|
||||
(super.noSuchMethod(Invocation.method(#GetCoinsToJoinSplit, [required]),
|
||||
returnValue: Future<dynamic>.value()) as _i8.Future<dynamic>);
|
||||
@override
|
||||
_i8.Future<int> EstimateJoinSplitFee(int? spendAmount) => (super.noSuchMethod(
|
||||
_i8.Future<int> estimateJoinSplitFee(int? spendAmount) => (super.noSuchMethod(
|
||||
Invocation.method(#EstimateJoinSplitFee, [spendAmount]),
|
||||
returnValue: Future<int>.value(0)) as _i8.Future<int>);
|
||||
@override
|
||||
|
|
Loading…
Reference in a new issue