Merge remote-tracking branch 'cypherstack/staging' into simplex
1
.github/workflows/test.yaml
vendored
|
@ -23,6 +23,7 @@ jobs:
|
|||
run: |
|
||||
cargo install cargo-ndk
|
||||
rustup target add x86_64-unknown-linux-gnu
|
||||
sudo apt clean
|
||||
sudo apt update
|
||||
sudo apt install -y unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm
|
||||
sudo apt install -y debhelper libclang-dev cargo rustc opencl-headers libssl-dev ocl-icd-opencl-dev
|
||||
|
|
BIN
assets/images/unclaimed.png
Normal file
After Width: | Height: | Size: 756 KiB |
|
@ -1,11 +1,18 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_908_22491)">
|
||||
<g opacity="0.4">
|
||||
<path d="M22.2 6C23.3297 5.37187 24 4.59422 24 3.75C24 1.67906 19.9688 0 15 0C9.98906 0 6 1.67906 6 3.75C6 4.59422 6.67031 5.37187 7.8 6C7.80937 6.00469 7.81758 6.00937 7.82578 6.01406C7.83398 6.01875 7.84219 6.02344 7.85156 6.02813C8.23125 6.00938 8.61094 6 9 6C11.6344 6 14.0906 6.44062 15.9422 7.21406C16.1203 7.28906 16.2984 7.36875 16.4672 7.44844C18.8062 7.28906 20.8359 6.75469 22.2 6Z" fill="#232323"/>
|
||||
<path d="M19.9435 12.9151C19.7958 12.9551 19.6477 12.9951 19.5 13.0359V13.5C20.7602 13.5 21.9296 13.8885 22.8951 14.5522C23.5995 14.0172 24 13.4028 24 12.75V11.0906C23.4141 11.5734 22.7063 11.9672 21.9422 12.2859C21.3382 12.5376 20.6447 12.7253 19.9435 12.9151Z" fill="#232323"/>
|
||||
<path d="M18.3703 8.74688C19.0031 9.37969 19.5 10.2234 19.5 11.25V11.4984C20.4328 11.2734 21.2625 10.9781 21.9469 10.6359C21.9739 10.6209 22.0009 10.6021 22.0279 10.5833C22.0852 10.5432 22.1426 10.5032 22.2 10.5C23.3297 9.87187 24 9.09375 24 8.25V6.59063C23.4141 7.07344 22.7063 7.46719 21.9422 7.78594C20.9109 8.2125 19.6969 8.54063 18.3703 8.74688Z" fill="#232323"/>
|
||||
<path d="M22.2 6C23.3297 5.37187 24 4.59422 24 3.75C24 1.67906 19.9688 0 15 0C9.98906 0 6 1.67906 6 3.75C6 4.59422 6.67031 5.37187 7.8 6C7.80937 6.00469 7.81758 6.00937 7.82578 6.01406C7.83398 6.01875 7.84219 6.02344 7.85156 6.02813C8.23125 6.00938 8.61094 6 9 6C11.6344 6 14.0906 6.44062 15.9422 7.21406C16.1203 7.28906 16.2984 7.36875 16.4672 7.44844C18.8062 7.28906 20.8359 6.75469 22.2 6Z" fill="#D12B41"/>
|
||||
<path d="M19.9435 12.9151C19.7958 12.9551 19.6477 12.9951 19.5 13.0359V13.5C20.7602 13.5 21.9296 13.8885 22.8951 14.5522C23.5995 14.0172 24 13.4028 24 12.75V11.0906C23.4141 11.5734 22.7063 11.9672 21.9422 12.2859C21.3382 12.5376 20.6447 12.7253 19.9435 12.9151Z" fill="#D12B41"/>
|
||||
<path d="M18.3703 8.74688C19.0031 9.37969 19.5 10.2234 19.5 11.25V11.4984C20.4328 11.2734 21.2625 10.9781 21.9469 10.6359C21.9739 10.6209 22.0009 10.6021 22.0279 10.5833C22.0852 10.5432 22.1426 10.5032 22.2 10.5C23.3297 9.87187 24 9.09375 24 8.25V6.59063C23.4141 7.07344 22.7063 7.46719 21.9422 7.78594C20.9109 8.2125 19.6969 8.54063 18.3703 8.74688Z" fill="#D12B41"/>
|
||||
</g>
|
||||
<path d="M16.2 13.5C17.3297 12.8719 18 12.0938 18 11.25C18 9.17813 13.9688 7.5 9 7.5C4.02938 7.5 0 9.17813 0 11.25C0 12.0938 0.669375 12.8719 1.79953 13.5C1.85443 13.5031 1.91057 13.5415 1.96782 13.5807C1.9966 13.6004 2.02567 13.6203 2.055 13.6359C3.70594 14.4703 6.20625 15 9 15C11.9438 15 14.5594 14.4094 16.2 13.5Z" fill="#232323"/>
|
||||
<path d="M14.8788 15.6729C13.1948 16.2046 11.1571 16.5 9 16.5C6.36562 16.5 3.91125 16.0594 2.05922 15.2859C1.29469 14.9672 0.583594 14.5734 0 14.0906V15.75C0 16.5938 0.669375 17.3719 1.79953 18C3.44109 18.9094 6.05625 19.5 9 19.5C10.6471 19.5 12.1916 19.3159 13.5211 18.9937C13.6261 17.7367 14.1186 16.5898 14.8788 15.6729Z" fill="#232323"/>
|
||||
<path d="M13.5862 20.5191C13.7529 21.4936 14.1547 22.3879 14.731 23.1415C13.1742 23.6778 11.1771 24 9 24C4.02938 24 0 22.3219 0 20.25V18.5906C0.583594 19.0734 1.29469 19.4672 2.05922 19.7859C3.91125 20.5594 6.36562 21 9 21C10.6307 21 12.1932 20.8312 13.5862 20.5191Z" fill="#232323"/>
|
||||
<path d="M24 19.5C24 21.9844 21.9844 24 19.5 24C17.0156 24 15 21.9844 15 19.5C15 17.0156 17.0156 15 19.5 15C21.9844 15 24 17.0156 24 19.5ZM19 17.4719V18.9719H17.5C17.225 18.9719 17 19.225 17 19.4719C17 19.775 17.225 19.9719 17.5 19.9719H19V21.4719C19 21.775 19.225 21.9719 19.5 21.9719C19.775 21.9719 20 21.775 20 21.4719V19.9719H21.5C21.775 19.9719 22 19.775 22 19.4719C22 19.225 21.775 18.9719 21.5 18.9719H20V17.4719C20 17.225 19.775 16.9719 19.5 16.9719C19.225 16.9719 19 17.225 19 17.4719Z" fill="#232323"/>
|
||||
<path d="M16.2 13.5C17.3297 12.8719 18 12.0938 18 11.25C18 9.17813 13.9688 7.5 9 7.5C4.02938 7.5 0 9.17813 0 11.25C0 12.0938 0.669375 12.8719 1.79953 13.5C1.85443 13.5031 1.91057 13.5415 1.96782 13.5807C1.9966 13.6004 2.02567 13.6203 2.055 13.6359C3.70594 14.4703 6.20625 15 9 15C11.9438 15 14.5594 14.4094 16.2 13.5Z" fill="#D12B41"/>
|
||||
<path d="M14.8788 15.6729C13.1948 16.2046 11.1571 16.5 9 16.5C6.36562 16.5 3.91125 16.0594 2.05922 15.2859C1.29469 14.9672 0.583594 14.5734 0 14.0906V15.75C0 16.5938 0.669375 17.3719 1.79953 18C3.44109 18.9094 6.05625 19.5 9 19.5C10.6471 19.5 12.1916 19.3159 13.5211 18.9937C13.6261 17.7367 14.1186 16.5898 14.8788 15.6729Z" fill="#D12B41"/>
|
||||
<path d="M13.5862 20.5191C13.7529 21.4936 14.1547 22.3879 14.731 23.1415C13.1742 23.6778 11.1771 24 9 24C4.02938 24 0 22.3219 0 20.25V18.5906C0.583594 19.0734 1.29469 19.4672 2.05922 19.7859C3.91125 20.5594 6.36562 21 9 21C10.6307 21 12.1932 20.8312 13.5862 20.5191Z" fill="#D12B41"/>
|
||||
<path d="M24 19.5C24 21.9844 21.9844 24 19.5 24C17.0156 24 15 21.9844 15 19.5C15 17.0156 17.0156 15 19.5 15C21.9844 15 24 17.0156 24 19.5ZM19 17.4719V18.9719H17.5C17.225 18.9719 17 19.225 17 19.4719C17 19.775 17.225 19.9719 17.5 19.9719H19V21.4719C19 21.775 19.225 21.9719 19.5 21.9719C19.775 21.9719 20 21.775 20 21.4719V19.9719H21.5C21.775 19.9719 22 19.775 22 19.4719C22 19.225 21.775 18.9719 21.5 18.9719H20V17.4719C20 17.225 19.775 16.9719 19.5 16.9719C19.225 16.9719 19 17.225 19 17.4719Z" fill="#D12B41"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_908_22491">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.7 KiB |
|
@ -1,4 +1,4 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.6695 1.06575C15.1855 0.607104 15.9756 0.65358 16.4343 1.16956L20.4343 5.66956C20.8552 6.14317 20.8552 6.85686 20.4343 7.33047L16.4343 11.8305C15.9756 12.3464 15.1855 12.3929 14.6695 11.9343C14.1536 11.4756 14.1071 10.6855 14.5657 10.1696L16.7164 7.75001H6C4.48122 7.75001 3.25 8.98123 3.25 10.5C3.25 11.1904 2.69036 11.75 2 11.75C1.30964 11.75 0.75 11.1904 0.75 10.5C0.75 7.60052 3.10051 5.25001 6 5.25001H16.7164L14.5657 2.83047C14.1071 2.31449 14.1536 1.5244 14.6695 1.06575Z" fill="#232323"/>
|
||||
<path opacity="0.4" fill-rule="evenodd" clip-rule="evenodd" d="M9.33045 23.4342C8.81448 23.8929 8.02439 23.8464 7.56574 23.3304L3.56574 18.8304C3.14475 18.3568 3.14475 17.6431 3.56574 17.1695L7.56574 12.6695C8.02439 12.1536 8.81448 12.1071 9.33046 12.5657C9.84643 13.0244 9.89291 13.8145 9.43426 14.3304L7.28355 16.75L18 16.75C19.5188 16.75 20.75 15.5188 20.75 14C20.75 13.3096 21.3096 12.75 22 12.75C22.6904 12.75 23.25 13.3096 23.25 14C23.25 16.8995 20.8995 19.25 18 19.25L7.28355 19.25L9.43426 21.6695C9.89291 22.1855 9.84643 22.9756 9.33045 23.4342Z" fill="#232323"/>
|
||||
<path d="M19.5 6.5H6C3.79086 6.5 2 8.29086 2 10.5M19.5 6.5L15.5 2M19.5 6.5L15.5 11" stroke="#D12B41" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path opacity="0.4" d="M4.5 18L18 18C20.2091 18 22 16.2091 22 14M4.5 18L8.5 22.5M4.5 18L8.5 13.5" stroke="#D12B41" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 455 B |
3
assets/svg/robot-head.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.4173 5.41699V5.91699H10.9173H14.1257C15.1181 5.91699 15.9173 6.71825 15.9173 7.70866V15.5003C15.9173 16.2377 15.3213 16.8337 14.584 16.8337H5.41732C4.67997 16.8337 4.08398 16.2377 4.08398 15.5003V7.70866C4.08398 6.71866 4.88565 5.91699 5.87565 5.91699H9.08398H9.58398V5.41699V3.58366C9.58398 3.35506 9.77205 3.16699 10.0007 3.16699C10.2292 3.16699 10.4173 3.35506 10.4173 3.58366V5.41699ZM8.16732 15.0837H8.58398H8.66732H9.08398H10.9173H11.334H11.4173H11.834H13.6673H14.1673V14.5837V13.667V13.167H13.6673H11.834H11.4173H11.334H10.9173H9.08398H8.66732H8.58398H8.16732H6.33398H5.83398V13.667V14.5837V15.0837H6.33398H8.16732ZM1.33398 10.0003C1.33398 9.89019 1.37745 9.7852 1.45595 9.70663C1.53524 9.62741 1.64114 9.58366 1.75065 9.58366H2.16732V14.0837H1.75065C1.64023 14.0837 1.53452 14.0398 1.45609 13.9614C1.37774 13.8831 1.33398 13.7775 1.33398 13.667V10.0003ZM5.60482 10.0003C5.60482 10.9095 6.34144 11.6462 7.25065 11.6462C8.15987 11.6462 8.89648 10.9095 8.89648 10.0003C8.89648 9.09111 8.15987 8.35449 7.25065 8.35449C6.34144 8.35449 5.60482 9.09111 5.60482 10.0003ZM11.1048 10.0003C11.1048 10.9101 11.8409 11.6462 12.7507 11.6462C13.6599 11.6462 14.3965 10.9095 14.3965 10.0003C14.3965 9.09111 13.6599 8.35449 12.7507 8.35449C11.8416 8.35449 11.1048 9.09037 11.1048 10.0003ZM18.2507 9.58366C18.3599 9.58366 18.4652 9.62719 18.5445 9.70648C18.6238 9.78578 18.6673 9.89112 18.6673 10.0003V13.667C18.6673 13.7774 18.6235 13.8831 18.5451 13.9616C18.4667 14.0399 18.3612 14.0837 18.2507 14.0837H17.834V9.58366H18.2507Z" fill="#8E9192" stroke="#8E9192"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
3
assets/svg/whirlpool.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.16811 18.29C4.45149 18.089 3.04304 17.5064 1.84171 16.5006C1.52855 16.2383 1.08929 15.7904 0.868214 15.5078C0.795124 15.4144 0.815885 15.4205 1.16357 15.5943C2.17257 16.0986 3.56529 16.3941 4.67986 16.3404C5.4666 16.3025 6.00361 16.1827 6.16709 16.0087C6.30087 15.8663 6.24663 15.772 5.85013 15.458C4.68271 14.5333 3.91789 13.4633 3.48109 12.1436C2.95876 10.5655 3.03006 8.85291 3.68121 7.33745C3.85982 6.92178 4.22353 6.25056 4.46108 5.89825C5.0569 5.01461 6.02359 4.09702 7.04772 3.443C7.53459 3.13207 8.67134 2.57608 9.28978 2.34638C10.1175 2.03895 11.0493 1.81907 11.9322 1.72278C12.5152 1.6592 13.8654 1.6973 14.3766 1.79177C15.7252 2.04096 16.8058 2.50254 17.8199 3.26249C18.1679 3.52329 18.8273 4.15363 19.0478 4.43636L19.1673 4.58953L18.7556 4.39158C17.6907 3.87949 16.4125 3.60951 15.2969 3.66106C13.9736 3.72218 13.4271 3.9979 13.8908 4.37051C14.4654 4.83219 14.718 5.05501 14.9678 5.32051C16.047 6.46745 16.6696 7.8829 16.8049 9.49648C16.8912 10.527 16.6998 11.6803 16.2805 12.6564C15.6851 14.0423 14.7972 15.1697 13.573 16.0943C13.0338 16.5014 12.6196 16.756 11.9526 17.09C10.795 17.6696 9.68552 18.033 8.49122 18.2239C8.12694 18.2821 6.49683 18.3285 6.16811 18.29ZM10.567 12.7788C11.6493 12.5494 12.5288 11.6694 12.7584 10.586C12.824 10.2766 12.8238 9.72298 12.7584 9.41267C12.6408 8.85789 12.4049 8.43337 11.9758 8.00424C11.3957 7.42417 10.7838 7.17097 9.95841 7.16954C9.16291 7.16743 8.43295 7.4925 7.88801 8.0894C7.39072 8.63413 7.14979 9.25773 7.14979 10.0002C7.14979 10.8011 7.40895 11.4213 7.98385 11.9962C8.40667 12.419 8.83591 12.6598 9.37601 12.7773C9.67249 12.8418 10.2667 12.8425 10.5673 12.7794L10.567 12.7788Z" fill="#8E9192"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -1 +1 @@
|
|||
Subproject commit 0309140a95a51388df0effcc39ff0a25b2752b29
|
||||
Subproject commit 9a150d8cd2c3625424b0059e6b7306f3659fdbe0
|
|
@ -1 +1 @@
|
|||
Subproject commit c1b403ccf6f4fffc9f7c233038c3df40f997c2b3
|
||||
Subproject commit af88796d5e4988c03422320c3842af5cf6c049ef
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/exceptions/main_db/main_db_exception.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
@ -28,7 +30,7 @@ class MainDB {
|
|||
AddressSchema,
|
||||
],
|
||||
directory: (await StackFileSystem.applicationIsarDirectory()).path,
|
||||
inspector: false,
|
||||
inspector: kDebugMode,
|
||||
name: "wallet_data",
|
||||
);
|
||||
return true;
|
||||
|
@ -39,39 +41,105 @@ class MainDB {
|
|||
String walletId) =>
|
||||
isar.addresses.where().walletIdEqualTo(walletId);
|
||||
|
||||
Future<void> putAddress(Address address) => isar.writeTxn(() async {
|
||||
await isar.addresses.put(address);
|
||||
Future<int> putAddress(Address address) async {
|
||||
try {
|
||||
return await isar.writeTxn(() async {
|
||||
return await isar.addresses.put(address);
|
||||
});
|
||||
} catch (e) {
|
||||
throw MainDBException("failed putAddress: $address", e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> putAddresses(List<Address> addresses) => isar.writeTxn(() async {
|
||||
await isar.addresses.putAll(addresses);
|
||||
Future<List<int>> putAddresses(List<Address> addresses) async {
|
||||
try {
|
||||
return await isar.writeTxn(() async {
|
||||
return await isar.addresses.putAll(addresses);
|
||||
});
|
||||
} catch (e) {
|
||||
throw MainDBException("failed putAddresses: $addresses", e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateAddress(Address oldAddress, Address newAddress) =>
|
||||
isar.writeTxn(() async {
|
||||
Future<List<int>> updateOrPutAddresses(List<Address> addresses) async {
|
||||
try {
|
||||
List<int> ids = [];
|
||||
await isar.writeTxn(() async {
|
||||
for (final address in addresses) {
|
||||
final storedAddress = await isar.addresses
|
||||
.getByValueWalletId(address.value, address.walletId);
|
||||
|
||||
int id;
|
||||
if (storedAddress == null) {
|
||||
id = await isar.addresses.put(address);
|
||||
} else {
|
||||
address.id = storedAddress.id;
|
||||
await storedAddress.transactions.load();
|
||||
final txns = storedAddress.transactions.toList();
|
||||
await isar.addresses.delete(storedAddress.id);
|
||||
id = await isar.addresses.put(address);
|
||||
address.transactions.addAll(txns);
|
||||
await address.transactions.save();
|
||||
}
|
||||
ids.add(id);
|
||||
}
|
||||
});
|
||||
return ids;
|
||||
} catch (e) {
|
||||
throw MainDBException("failed updateOrPutAddresses: $addresses", e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Address?> getAddress(String walletId, String address) async {
|
||||
return isar.addresses.getByValueWalletId(address, walletId);
|
||||
}
|
||||
|
||||
Future<int> updateAddress(Address oldAddress, Address newAddress) async {
|
||||
try {
|
||||
return await isar.writeTxn(() async {
|
||||
newAddress.id = oldAddress.id;
|
||||
await oldAddress.transactions.load();
|
||||
final txns = oldAddress.transactions.toList();
|
||||
await isar.addresses.delete(oldAddress.id);
|
||||
await isar.addresses.put(newAddress);
|
||||
final id = await isar.addresses.put(newAddress);
|
||||
newAddress.transactions.addAll(txns);
|
||||
await newAddress.transactions.save();
|
||||
return id;
|
||||
});
|
||||
} catch (e) {
|
||||
throw MainDBException(
|
||||
"failed updateAddress: from=$oldAddress to=$newAddress", e);
|
||||
}
|
||||
}
|
||||
|
||||
// transactions
|
||||
QueryBuilder<Transaction, Transaction, QAfterWhereClause> getTransactions(
|
||||
String walletId) =>
|
||||
isar.transactions.where().walletIdEqualTo(walletId);
|
||||
|
||||
Future<void> putTransaction(Transaction transaction) =>
|
||||
isar.writeTxn(() async {
|
||||
await isar.transactions.put(transaction);
|
||||
Future<int> putTransaction(Transaction transaction) async {
|
||||
try {
|
||||
return await isar.writeTxn(() async {
|
||||
return await isar.transactions.put(transaction);
|
||||
});
|
||||
} catch (e) {
|
||||
throw MainDBException("failed putTransaction: $transaction", e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> putTransactions(List<Transaction> transactions) =>
|
||||
isar.writeTxn(() async {
|
||||
await isar.transactions.putAll(transactions);
|
||||
Future<List<int>> putTransactions(List<Transaction> transactions) async {
|
||||
try {
|
||||
return await isar.writeTxn(() async {
|
||||
return await isar.transactions.putAll(transactions);
|
||||
});
|
||||
} catch (e) {
|
||||
throw MainDBException("failed putTransactions: $transactions", e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Transaction?> getTransaction(String walletId, String txid) async {
|
||||
return isar.transactions.getByTxidWalletId(txid, walletId);
|
||||
}
|
||||
|
||||
// utxos
|
||||
QueryBuilder<UTXO, UTXO, QAfterWhereClause> getUTXOs(String walletId) =>
|
||||
|
@ -182,55 +250,60 @@ class MainDB {
|
|||
}
|
||||
|
||||
Future<void> addNewTransactionData(
|
||||
List<Tuple4<Transaction, List<Output>, List<Input>, Address?>>
|
||||
transactionsData,
|
||||
String walletId) async {
|
||||
await isar.writeTxn(() async {
|
||||
for (final data in transactionsData) {
|
||||
final tx = data.item1;
|
||||
List<Tuple4<Transaction, List<Output>, List<Input>, Address?>>
|
||||
transactionsData,
|
||||
String walletId,
|
||||
) async {
|
||||
try {
|
||||
await isar.writeTxn(() async {
|
||||
for (final data in transactionsData) {
|
||||
final tx = data.item1;
|
||||
|
||||
final potentiallyUnconfirmedTx = await getTransactions(walletId)
|
||||
.filter()
|
||||
.txidEqualTo(tx.txid)
|
||||
.findFirst();
|
||||
if (potentiallyUnconfirmedTx != null) {
|
||||
// update use id to replace tx
|
||||
tx.id = potentiallyUnconfirmedTx.id;
|
||||
await isar.transactions.delete(potentiallyUnconfirmedTx.id);
|
||||
}
|
||||
// save transaction
|
||||
await isar.transactions.put(tx);
|
||||
|
||||
// link and save outputs
|
||||
if (data.item2.isNotEmpty) {
|
||||
await isar.outputs.putAll(data.item2);
|
||||
tx.outputs.addAll(data.item2);
|
||||
await tx.outputs.save();
|
||||
}
|
||||
|
||||
// link and save inputs
|
||||
if (data.item3.isNotEmpty) {
|
||||
await isar.inputs.putAll(data.item3);
|
||||
tx.inputs.addAll(data.item3);
|
||||
await tx.inputs.save();
|
||||
}
|
||||
|
||||
if (data.item4 != null) {
|
||||
final address = await getAddresses(walletId)
|
||||
final potentiallyUnconfirmedTx = await getTransactions(walletId)
|
||||
.filter()
|
||||
.valueEqualTo(data.item4!.value)
|
||||
.txidEqualTo(tx.txid)
|
||||
.findFirst();
|
||||
if (potentiallyUnconfirmedTx != null) {
|
||||
// update use id to replace tx
|
||||
tx.id = potentiallyUnconfirmedTx.id;
|
||||
await isar.transactions.delete(potentiallyUnconfirmedTx.id);
|
||||
}
|
||||
// save transaction
|
||||
await isar.transactions.put(tx);
|
||||
|
||||
// check if address exists in db and add if it does not
|
||||
if (address == null) {
|
||||
await isar.addresses.put(data.item4!);
|
||||
// link and save outputs
|
||||
if (data.item2.isNotEmpty) {
|
||||
await isar.outputs.putAll(data.item2);
|
||||
tx.outputs.addAll(data.item2);
|
||||
await tx.outputs.save();
|
||||
}
|
||||
|
||||
// link and save address
|
||||
tx.address.value = address ?? data.item4!;
|
||||
await tx.address.save();
|
||||
// link and save inputs
|
||||
if (data.item3.isNotEmpty) {
|
||||
await isar.inputs.putAll(data.item3);
|
||||
tx.inputs.addAll(data.item3);
|
||||
await tx.inputs.save();
|
||||
}
|
||||
|
||||
if (data.item4 != null) {
|
||||
final address = await getAddresses(walletId)
|
||||
.filter()
|
||||
.valueEqualTo(data.item4!.value)
|
||||
.findFirst();
|
||||
|
||||
// check if address exists in db and add if it does not
|
||||
if (address == null) {
|
||||
await isar.addresses.put(data.item4!);
|
||||
}
|
||||
|
||||
// link and save address
|
||||
tx.address.value = address ?? data.item4!;
|
||||
await tx.address.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
throw MainDBException("failed addNewTransactionData", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'dart:io';
|
|||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/rpc.dart';
|
||||
import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
@ -132,7 +133,17 @@ class ElectrumX {
|
|||
final response = await _rpcClient!.request(jsonRequestString);
|
||||
|
||||
if (response["error"] != null) {
|
||||
throw Exception("JSONRPC response error: $response");
|
||||
if (response["error"]
|
||||
.toString()
|
||||
.contains("No such mempool or blockchain transaction")) {
|
||||
throw NoSuchTransactionException(
|
||||
"No such mempool or blockchain transaction",
|
||||
args.first.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
throw Exception(
|
||||
"JSONRPC response \ncommand: $command \nargs: $args \nerror: $response");
|
||||
}
|
||||
|
||||
currentFailoverIndex = -1;
|
||||
|
@ -544,6 +555,10 @@ class ElectrumX {
|
|||
verbose,
|
||||
],
|
||||
);
|
||||
if (!verbose) {
|
||||
return {"rawtx": response["result"] as String};
|
||||
}
|
||||
|
||||
return Map<String, dynamic>.from(response["result"] as Map);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
|
|
5
lib/exceptions/address/address_exception.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
import 'package:stackwallet/exceptions/sw_exception.dart';
|
||||
|
||||
class AddressException extends SWException {
|
||||
AddressException(super.message);
|
||||
}
|
7
lib/exceptions/electrumx/no_such_transaction.dart
Normal file
|
@ -0,0 +1,7 @@
|
|||
import 'package:stackwallet/exceptions/sw_exception.dart';
|
||||
|
||||
class NoSuchTransactionException extends SWException {
|
||||
final String txid;
|
||||
|
||||
NoSuchTransactionException(super.message, this.txid);
|
||||
}
|
12
lib/exceptions/main_db/main_db_exception.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import 'package:stackwallet/exceptions/sw_exception.dart';
|
||||
|
||||
class MainDBException extends SWException {
|
||||
MainDBException(super.message, this.originalError);
|
||||
|
||||
final Object originalError;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "$message: originalError=$originalError";
|
||||
}
|
||||
}
|
11
lib/exceptions/sw_exception.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
// generic stack wallet exception which all other custom exceptions should
|
||||
// extend from
|
||||
|
||||
class SWException with Exception {
|
||||
SWException(this.message);
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
toString() => message;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import 'package:stackwallet/exceptions/sw_exception.dart';
|
||||
|
||||
class InsufficientBalanceException extends SWException {
|
||||
InsufficientBalanceException(super.message);
|
||||
}
|
5
lib/exceptions/wallet/paynym_send_exception.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
import 'package:stackwallet/exceptions/sw_exception.dart';
|
||||
|
||||
class PaynymSendException extends SWException {
|
||||
PaynymSendException(super.message);
|
||||
}
|
|
@ -254,6 +254,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
_desktopHasPassword =
|
||||
await ref.read(storageCryptoHandlerProvider).hasPassword();
|
||||
}
|
||||
await MainDB.instance.initMainDB();
|
||||
}
|
||||
|
||||
Future<void> load() async {
|
||||
|
@ -267,8 +268,6 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
await loadShared();
|
||||
}
|
||||
|
||||
await MainDB.instance.initMainDB();
|
||||
|
||||
_notificationsService = ref.read(notificationsProvider);
|
||||
_nodeService = ref.read(nodeServiceChangeNotifierProvider);
|
||||
_tradesService = ref.read(tradesServiceProvider);
|
||||
|
@ -344,12 +343,12 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
case "oceanBreeze":
|
||||
colorTheme = OceanBreezeColors();
|
||||
break;
|
||||
case "light":
|
||||
colorTheme = LightColors();
|
||||
break;
|
||||
case "fruitSorbet":
|
||||
default:
|
||||
colorTheme = FruitSorbetColors();
|
||||
break;
|
||||
case "light":
|
||||
default:
|
||||
colorTheme = LightColors();
|
||||
}
|
||||
loadingCompleter = Completer();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/exceptions/address/address_exception.dart';
|
||||
import 'package:stackwallet/models/isar/models/address/crypto_currency_address.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||
|
||||
part 'address.g.dart';
|
||||
|
||||
class AddressException extends SWException {
|
||||
AddressException(super.message);
|
||||
}
|
||||
|
||||
@Collection(accessor: "addresses")
|
||||
class Address extends CryptoCurrencyAddress {
|
||||
Address({
|
||||
|
@ -73,6 +69,7 @@ class Address extends CryptoCurrencyAddress {
|
|||
"}";
|
||||
}
|
||||
|
||||
// do not modify
|
||||
enum AddressType {
|
||||
p2pkh,
|
||||
p2sh,
|
||||
|
@ -83,6 +80,7 @@ enum AddressType {
|
|||
nonWallet,
|
||||
}
|
||||
|
||||
// do not modify
|
||||
enum AddressSubType {
|
||||
receiving,
|
||||
change,
|
||||
|
|
|
@ -584,7 +584,7 @@ class _AddAddressBookEntryViewState
|
|||
"Address ${i + 1}",
|
||||
style: STextStyles.smallMed12(context),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
onTap: () {
|
||||
_removeForm(forms[i].id);
|
||||
},
|
||||
|
@ -601,7 +601,7 @@ class _AddAddressBookEntryViewState
|
|||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
onTap: () {
|
||||
_addForm();
|
||||
scrollController.animateTo(
|
||||
|
|
|
@ -311,7 +311,7 @@ class _ContactDetailsViewState extends ConsumerState<ContactDetailsView> {
|
|||
"Addresses",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Add new",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
|
|
|
@ -122,12 +122,12 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
setState(() {
|
||||
_amountOutOfRangeErrorString = "Invalid amount";
|
||||
});
|
||||
} else if (value > maxFiat) {
|
||||
} else if (value > maxFiat && buyWithFiat) {
|
||||
setState(() {
|
||||
_amountOutOfRangeErrorString =
|
||||
"Maximum amount: ${maxFiat.toStringAsFixed(2)}";
|
||||
});
|
||||
} else if (value < minFiat) {
|
||||
} else if (value < minFiat && buyWithFiat) {
|
||||
setState(() {
|
||||
_amountOutOfRangeErrorString =
|
||||
"Minimum amount: ${minFiat.toStringAsFixed(2)}";
|
||||
|
@ -280,6 +280,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
minFiat = fiat.minAmount != minFiat ? fiat.minAmount : minFiat;
|
||||
maxFiat = fiat.maxAmount != maxFiat ? fiat.maxAmount : maxFiat;
|
||||
});
|
||||
validateAmount();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -530,25 +531,25 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
}
|
||||
} else {
|
||||
// Error; probably amount out of bounds
|
||||
String errorMessage = "${quoteResponse.exception?.errorMessage}";
|
||||
if (errorMessage.contains('must be between')) {
|
||||
errorMessage = errorMessage.substring(
|
||||
errorMessage.indexOf('getQuote exception: ') + 20,
|
||||
errorMessage.indexOf(", value: null"));
|
||||
_BuyFormState.boundedCryptoTicker = errorMessage.substring(
|
||||
errorMessage.indexOf('The ') + 4,
|
||||
errorMessage.indexOf(' amount must be between'));
|
||||
_BuyFormState.minCrypto = Decimal.parse(errorMessage.substring(
|
||||
errorMessage.indexOf('must be between ') + 16,
|
||||
errorMessage.indexOf(' and ')));
|
||||
_BuyFormState.maxCrypto = Decimal.parse(errorMessage.substring(
|
||||
errorMessage.indexOf("$minCrypto and ") + "$minCrypto and ".length,
|
||||
errorMessage.length));
|
||||
if (Decimal.parse(_buyAmountController.text) >
|
||||
_BuyFormState.maxCrypto) {
|
||||
_buyAmountController.text = _BuyFormState.maxCrypto.toString();
|
||||
}
|
||||
}
|
||||
// String errorMessage = "${quoteResponse.exception?.errorMessage}";
|
||||
// if (errorMessage.contains('must be between')) {
|
||||
// errorMessage = errorMessage.substring(
|
||||
// errorMessage.indexOf('getQuote exception: ') + 20,
|
||||
// errorMessage.indexOf(", value: null"));
|
||||
// _BuyFormState.boundedCryptoTicker = errorMessage.substring(
|
||||
// errorMessage.indexOf('The ') + 4,
|
||||
// errorMessage.indexOf(' amount must be between'));
|
||||
// _BuyFormState.minCrypto = Decimal.parse(errorMessage.substring(
|
||||
// errorMessage.indexOf('must be between ') + 16,
|
||||
// errorMessage.indexOf(' and ')));
|
||||
// _BuyFormState.maxCrypto = Decimal.parse(errorMessage.substring(
|
||||
// errorMessage.indexOf("$minCrypto and ") + "$minCrypto and ".length,
|
||||
// errorMessage.length));
|
||||
// if (Decimal.parse(_buyAmountController.text) >
|
||||
// _BuyFormState.maxCrypto) {
|
||||
// _buyAmountController.text = _BuyFormState.maxCrypto.toString();
|
||||
// }
|
||||
// }
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
|
@ -570,7 +571,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
height: 24,
|
||||
),
|
||||
Text(
|
||||
errorMessage,
|
||||
quoteResponse.exception!.errorMessage,
|
||||
style: STextStyles.smallMed14(context),
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -632,10 +633,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
level: LogLevel.Warning,
|
||||
);
|
||||
return BuyResponse(
|
||||
exception: BuyException(
|
||||
response.toString(),
|
||||
BuyExceptionType.generic,
|
||||
),
|
||||
exception: response.exception ??
|
||||
BuyException(
|
||||
response.toString(),
|
||||
BuyExceptionType.generic,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -951,12 +953,13 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: buyWithFiat ? "Use crypto amount" : "Use fiat amount",
|
||||
onTap: () {
|
||||
setState(() {
|
||||
buyWithFiat = !buyWithFiat;
|
||||
});
|
||||
validateAmount();
|
||||
},
|
||||
)
|
||||
],
|
||||
|
@ -1126,8 +1129,8 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
),
|
||||
),
|
||||
if (isStackCoin(selectedCrypto?.ticker))
|
||||
BlueTextButton(
|
||||
text: "Choose from stack",
|
||||
CustomTextButton(
|
||||
text: "Choose from Stack",
|
||||
onTap: () {
|
||||
try {
|
||||
final coin = coinFromTickerCaseInsensitive(
|
||||
|
@ -1150,7 +1153,11 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
_receiveAddressController.text =
|
||||
await manager.currentReceivingAddress;
|
||||
|
||||
setState(() {});
|
||||
setState(() {
|
||||
_addressToggleFlag =
|
||||
_receiveAddressController.text.isNotEmpty;
|
||||
});
|
||||
validateAmount();
|
||||
}
|
||||
});
|
||||
} catch (e, s) {
|
||||
|
|
|
@ -18,16 +18,28 @@ import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
|||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
|
||||
class BuyWarningPopup extends StatelessWidget {
|
||||
BuyWarningPopup({
|
||||
class BuyWarningPopup extends StatefulWidget {
|
||||
const BuyWarningPopup({
|
||||
Key? key,
|
||||
required this.quote,
|
||||
this.order,
|
||||
}) : super(key: key);
|
||||
|
||||
final SimplexQuote quote;
|
||||
final SimplexOrder? order;
|
||||
@override
|
||||
State<BuyWarningPopup> createState() => _BuyWarningPopupState();
|
||||
}
|
||||
|
||||
class _BuyWarningPopupState extends State<BuyWarningPopup> {
|
||||
late final bool isDesktop;
|
||||
SimplexOrder? order;
|
||||
|
||||
String get title => "Buy ${widget.quote.crypto.ticker}";
|
||||
String get message =>
|
||||
"This purchase is provided and fulfilled by Simplex by nuvei "
|
||||
"(a third party). You will be taken to their website. Please follow "
|
||||
"their instructions.";
|
||||
|
||||
Future<BuyResponse<SimplexOrder>> newOrder(SimplexQuote quote) async {
|
||||
final orderResponse = await SimplexAPI.instance.newOrder(quote);
|
||||
|
||||
|
@ -38,174 +50,241 @@ class BuyWarningPopup extends StatelessWidget {
|
|||
return SimplexAPI.instance.redirect(order);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
Future<void> _buyInvoice() async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
// useRootNavigator: isDesktop,
|
||||
builder: (context) {
|
||||
return isDesktop
|
||||
? DesktopDialog(
|
||||
maxHeight: 700,
|
||||
maxWidth: 580,
|
||||
child: Column(
|
||||
Future<void> _buyInvoice() async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
// useRootNavigator: isDesktop,
|
||||
builder: (context) {
|
||||
return isDesktop
|
||||
? DesktopDialog(
|
||||
maxHeight: 700,
|
||||
maxWidth: 580,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
),
|
||||
child: Text(
|
||||
"Order details",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 32,
|
||||
bottom: 32,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
),
|
||||
child: Text(
|
||||
"Order details",
|
||||
style: STextStyles.desktopH3(context),
|
||||
Expanded(
|
||||
child: RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(16),
|
||||
borderColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.background,
|
||||
child: BuyOrderDetailsView(
|
||||
order: order as SimplexOrder,
|
||||
),
|
||||
),
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: BuyOrderDetailsView(
|
||||
order: order as SimplexOrder,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> onContinue() async {
|
||||
BuyResponse<SimplexOrder> orderResponse = await newOrder(widget.quote);
|
||||
if (orderResponse.exception == null) {
|
||||
await redirect(orderResponse.value as SimplexOrder)
|
||||
.then((_response) async {
|
||||
order = orderResponse.value as SimplexOrder;
|
||||
Navigator.of(context, rootNavigator: isDesktop).pop();
|
||||
Navigator.of(context, rootNavigator: isDesktop).pop();
|
||||
await _buyInvoice();
|
||||
});
|
||||
} else {
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
if (isDesktop) {
|
||||
return DesktopDialog(
|
||||
maxWidth: 450,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Simplex API error",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
Text(
|
||||
"${orderResponse.exception?.errorMessage}",
|
||||
style: STextStyles.smallMed14(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 56,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 32,
|
||||
bottom: 32,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(16),
|
||||
borderColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.background,
|
||||
child: BuyOrderDetailsView(
|
||||
order: order as SimplexOrder,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: PrimaryButton(
|
||||
buttonHeight: ButtonHeight.l,
|
||||
label: "Ok",
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop(); // weee
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: BuyOrderDetailsView(
|
||||
order: order as SimplexOrder,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return StackDialog(
|
||||
title: "Buy ${quote.crypto.ticker}",
|
||||
message: "This purchase is provided and fulfilled by Simplex by nuvei "
|
||||
"(a third party). You will be taken to their website. Please follow "
|
||||
"their instructions.",
|
||||
leftButton: SecondaryButton(
|
||||
label: "Cancel",
|
||||
onPressed: Navigator.of(context, rootNavigator: isDesktop).pop,
|
||||
),
|
||||
rightButton: PrimaryButton(
|
||||
label: "Continue",
|
||||
onPressed: () async {
|
||||
BuyResponse<SimplexOrder> orderResponse = await newOrder(quote);
|
||||
if (orderResponse.exception == null) {
|
||||
await redirect(orderResponse.value as SimplexOrder)
|
||||
.then((_response) async {
|
||||
this.order = orderResponse.value as SimplexOrder;
|
||||
Navigator.of(context, rootNavigator: isDesktop).pop();
|
||||
Navigator.of(context, rootNavigator: isDesktop).pop();
|
||||
await _buyInvoice();
|
||||
});
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
if (isDesktop) {
|
||||
return DesktopDialog(
|
||||
maxWidth: 450,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Simplex API error",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
Text(
|
||||
"${orderResponse.exception?.errorMessage}",
|
||||
style: STextStyles.smallMed14(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 56,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
buttonHeight: ButtonHeight.l,
|
||||
label: "Ok",
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop(); // weee
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return StackDialog(
|
||||
title: "Simplex API error",
|
||||
message: "${orderResponse.exception?.errorMessage}",
|
||||
// "${quoteResponse.exception?.errorMessage.substring(8, (quoteResponse.exception?.errorMessage?.length ?? 109) - (8 + 6))}",
|
||||
rightButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
return StackDialog(
|
||||
title: "Simplex API error",
|
||||
message: "${orderResponse.exception?.errorMessage}",
|
||||
// "${quoteResponse.exception?.errorMessage.substring(8, (quoteResponse.exception?.errorMessage?.length ?? 109) - (8 + 6))}",
|
||||
rightButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getSecondaryEnabledButtonStyle(context),
|
||||
child: Text(
|
||||
"Ok",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getSecondaryEnabledButtonStyle(context),
|
||||
child: Text(
|
||||
"Ok",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop(); // weee
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
.accentColorDark),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop(); // weee
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
icon: SizedBox(
|
||||
width: 64,
|
||||
height: 32,
|
||||
child: SvgPicture.asset(
|
||||
Assets.buy.simplexLogo(context),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
order = widget.order;
|
||||
isDesktop = Util.isDesktop;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isDesktop) {
|
||||
return DesktopDialog(
|
||||
maxWidth: 580,
|
||||
maxHeight: 350,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
SizedBox(
|
||||
width: 64,
|
||||
height: 32,
|
||||
child: SvgPicture.asset(
|
||||
Assets.buy.simplexLogo(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
message,
|
||||
style: STextStyles.desktopTextSmall(context),
|
||||
),
|
||||
const Spacer(
|
||||
flex: 2,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Cancel",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
onPressed:
|
||||
Navigator.of(context, rootNavigator: isDesktop).pop,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
buttonHeight: ButtonHeight.l,
|
||||
label: "Continue",
|
||||
onPressed: onContinue,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
} else {
|
||||
return StackDialog(
|
||||
title: title,
|
||||
message: message,
|
||||
leftButton: SecondaryButton(
|
||||
label: "Cancel",
|
||||
onPressed: Navigator.of(context, rootNavigator: isDesktop).pop,
|
||||
),
|
||||
rightButton: PrimaryButton(
|
||||
label: "Continue",
|
||||
onPressed: onContinue,
|
||||
),
|
||||
icon: SizedBox(
|
||||
width: 64,
|
||||
height: 32,
|
||||
child: SvgPicture.asset(
|
||||
Assets.buy.simplexLogo(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -189,8 +189,8 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
style: STextStyles.smallMed12(context),
|
||||
),
|
||||
if (isStackCoin(model.receiveTicker))
|
||||
BlueTextButton(
|
||||
text: "Choose from stack",
|
||||
CustomTextButton(
|
||||
text: "Choose from Stack",
|
||||
onTap: () {
|
||||
try {
|
||||
final coin =
|
||||
|
@ -448,8 +448,8 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
style: STextStyles.smallMed12(context),
|
||||
),
|
||||
if (isStackCoin(model.sendTicker))
|
||||
BlueTextButton(
|
||||
text: "Choose from stack",
|
||||
CustomTextButton(
|
||||
text: "Choose from Stack",
|
||||
onTap: () {
|
||||
try {
|
||||
final coin =
|
||||
|
|
|
@ -516,7 +516,7 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
|||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "View transaction",
|
||||
onTap: () {
|
||||
final Coin coin =
|
||||
|
|
|
@ -50,7 +50,7 @@ class _HomeViewState extends ConsumerState<HomeView> {
|
|||
|
||||
Future<bool> _onWillPop() async {
|
||||
// go to home view when tapping back on the main exchange view
|
||||
if (ref.read(homeViewPageIndexStateProvider.state).state == 1) {
|
||||
if (ref.read(homeViewPageIndexStateProvider.state).state != 0) {
|
||||
ref.read(homeViewPageIndexStateProvider.state).state = 0;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -5,16 +5,17 @@ import 'package:flutter/services.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:stackwallet/exceptions/wallet/insufficient_balance_exception.dart';
|
||||
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/paynym/dialogs/confirm_paynym_connect_dialog.dart';
|
||||
import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
|
||||
import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart';
|
||||
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
|
||||
import 'package:stackwallet/pages/send_view/send_view.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
|
@ -25,6 +26,7 @@ import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
|||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class PaynymDetailsPopup extends ConsumerStatefulWidget {
|
||||
const PaynymDetailsPopup({
|
||||
|
@ -43,6 +45,19 @@ class PaynymDetailsPopup extends ConsumerStatefulWidget {
|
|||
class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
||||
bool _showInsufficientFundsInfo = false;
|
||||
|
||||
Future<void> _onSend() async {
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(widget.walletId);
|
||||
await Navigator.of(context).pushNamed(
|
||||
SendView.routeName,
|
||||
arguments: Tuple3(
|
||||
manager.walletId,
|
||||
manager.coin,
|
||||
widget.accountLite,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onConnectPressed() async {
|
||||
bool canPop = false;
|
||||
unawaited(
|
||||
|
@ -57,30 +72,24 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
),
|
||||
);
|
||||
|
||||
final wallet = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as DogecoinWallet;
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(widget.walletId);
|
||||
|
||||
// sanity check to prevent second notifcation tx
|
||||
if (wallet.hasConnectedConfirmed(widget.accountLite.code)) {
|
||||
canPop = true;
|
||||
Navigator.of(context).pop();
|
||||
// TODO show info popup
|
||||
return;
|
||||
} else if (wallet.hasConnected(widget.accountLite.code)) {
|
||||
final wallet = manager.wallet as PaynymWalletInterface;
|
||||
|
||||
if (await wallet.hasConnected(widget.accountLite.code)) {
|
||||
canPop = true;
|
||||
Navigator.of(context).pop();
|
||||
// TODO show info popup
|
||||
return;
|
||||
}
|
||||
|
||||
final rates = await wallet.fees;
|
||||
final rates = await manager.fees;
|
||||
|
||||
Map<String, dynamic> preparedTx;
|
||||
|
||||
try {
|
||||
preparedTx = await wallet.buildNotificationTx(
|
||||
preparedTx = await wallet.prepareNotificationTx(
|
||||
selectedTxFeeRate: rates.medium,
|
||||
targetPaymentCodeString: widget.accountLite.code,
|
||||
);
|
||||
|
@ -117,7 +126,7 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
Navigator.of(context).push(
|
||||
RouteGenerator.getRoute(
|
||||
builder: (_) => ConfirmTransactionView(
|
||||
walletId: wallet.walletId,
|
||||
walletId: manager.walletId,
|
||||
routeOnSuccessName: PaynymHomeView.routeName,
|
||||
isPaynymNotificationTransaction: true,
|
||||
transactionInfo: {
|
||||
|
@ -133,7 +142,7 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
);
|
||||
},
|
||||
amount: (preparedTx["amount"] as int) + (preparedTx["fee"] as int),
|
||||
coin: wallet.coin,
|
||||
coin: manager.coin,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -141,6 +150,11 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final manager = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId)));
|
||||
|
||||
final wallet = manager.wallet as PaynymWalletInterface;
|
||||
|
||||
return DesktopDialog(
|
||||
maxWidth: MediaQuery.of(context).size.width - 32,
|
||||
maxHeight: double.infinity,
|
||||
|
@ -162,31 +176,97 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
children: [
|
||||
PayNymBot(
|
||||
paymentCodeString: widget.accountLite.code,
|
||||
size: 32,
|
||||
size: 36,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
widget.accountLite.nymName,
|
||||
style: STextStyles.w600_12(context),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.accountLite.nymName,
|
||||
style: STextStyles.w600_14(context),
|
||||
),
|
||||
FutureBuilder(
|
||||
future:
|
||||
wallet.hasConnected(widget.accountLite.code),
|
||||
builder: (context, AsyncSnapshot<bool> snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done &&
|
||||
snapshot.data == true) {
|
||||
return Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Text(
|
||||
"Connected",
|
||||
style: STextStyles.w500_12(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorGreen,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Connect",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circlePlusFilled,
|
||||
width: 10,
|
||||
height: 10,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextPrimary,
|
||||
),
|
||||
iconSpacing: 4,
|
||||
width: 86,
|
||||
onPressed: _onConnectPressed,
|
||||
FutureBuilder(
|
||||
future: wallet.hasConnected(widget.accountLite.code),
|
||||
builder: (context, AsyncSnapshot<bool> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
if (snapshot.data!) {
|
||||
return PrimaryButton(
|
||||
label: "Send",
|
||||
buttonHeight: ButtonHeight.xl,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circleArrowUpRight,
|
||||
width: 14,
|
||||
height: 14,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextPrimary,
|
||||
),
|
||||
iconSpacing: 8,
|
||||
width: 100,
|
||||
onPressed: _onSend,
|
||||
);
|
||||
} else {
|
||||
return PrimaryButton(
|
||||
label: "Connect",
|
||||
buttonHeight: ButtonHeight.xl,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circlePlusFilled,
|
||||
width: 13,
|
||||
height: 13,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextPrimary,
|
||||
),
|
||||
iconSpacing: 8,
|
||||
width: 128,
|
||||
onPressed: _onConnectPressed,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const SizedBox(
|
||||
height: 32,
|
||||
child: LoadingIndicator(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -211,6 +291,7 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.warningForeground,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -241,7 +322,9 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
children: [
|
||||
Text(
|
||||
"PayNym address",
|
||||
style: STextStyles.infoSmall(context),
|
||||
style: STextStyles.infoSmall(context).copyWith(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
|
@ -252,6 +335,7 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -266,7 +350,7 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
),
|
||||
QrImage(
|
||||
padding: const EdgeInsets.all(0),
|
||||
size: 86,
|
||||
size: 100,
|
||||
data: widget.accountLite.code,
|
||||
foregroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.textDark,
|
||||
|
@ -295,16 +379,16 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Copy",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
buttonHeight: ButtonHeight.xl,
|
||||
iconSpacing: 8,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
width: 10,
|
||||
height: 10,
|
||||
width: 12,
|
||||
height: 12,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondary,
|
||||
),
|
||||
iconSpacing: 4,
|
||||
onPressed: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(
|
||||
|
|
|
@ -56,7 +56,7 @@ class PaynymQrPopup extends StatelessWidget {
|
|||
children: [
|
||||
PayNymBot(
|
||||
paymentCodeString: paynymAccount.codes.first.code,
|
||||
size: isDesktop ? 56 : 32,
|
||||
size: isDesktop ? 56 : 36,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
|
@ -65,7 +65,7 @@ class PaynymQrPopup extends StatelessWidget {
|
|||
paynymAccount.nymName,
|
||||
style: isDesktop
|
||||
? STextStyles.w500_24(context)
|
||||
: STextStyles.w600_12(context),
|
||||
: STextStyles.w600_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -87,7 +87,7 @@ class PaynymQrPopup extends StatelessWidget {
|
|||
children: [
|
||||
Expanded(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(minHeight: 107),
|
||||
constraints: const BoxConstraints(minHeight: 130),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
@ -100,7 +100,9 @@ class PaynymQrPopup extends StatelessWidget {
|
|||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.infoSmall(context),
|
||||
: STextStyles.infoSmall(context).copyWith(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
|
@ -113,14 +115,15 @@ class PaynymQrPopup extends StatelessWidget {
|
|||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Copy",
|
||||
textSize: isDesktop ? 18 : 10,
|
||||
textSize: isDesktop ? 18 : 14,
|
||||
onTap: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(
|
||||
|
@ -146,7 +149,7 @@ class PaynymQrPopup extends StatelessWidget {
|
|||
),
|
||||
QrImage(
|
||||
padding: const EdgeInsets.all(0),
|
||||
size: 107,
|
||||
size: 130,
|
||||
data: paynymAccount.codes.first.code,
|
||||
foregroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.textDark,
|
||||
|
|
|
@ -9,9 +9,9 @@ import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
|||
import 'package:stackwallet/providers/global/paynym_api_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart';
|
||||
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
|
@ -117,7 +117,7 @@ class _PaynymClaimViewState extends ConsumerState<PaynymClaimView> {
|
|||
),
|
||||
Image(
|
||||
image: AssetImage(
|
||||
Assets.png.stack,
|
||||
Assets.png.unclaimedPaynym,
|
||||
),
|
||||
width: MediaQuery.of(context).size.width / 2,
|
||||
),
|
||||
|
@ -159,16 +159,18 @@ class _PaynymClaimViewState extends ConsumerState<PaynymClaimView> {
|
|||
).then((value) => shouldCancel = value == true),
|
||||
);
|
||||
|
||||
// get wallet to access paynym calls
|
||||
final wallet = ref
|
||||
final manager = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as DogecoinWallet;
|
||||
.getManager(widget.walletId);
|
||||
|
||||
// get wallet to access paynym calls
|
||||
final wallet = manager.wallet as PaynymWalletInterface;
|
||||
|
||||
if (shouldCancel) return;
|
||||
|
||||
// get payment code
|
||||
final pCode = await wallet.getPaymentCode();
|
||||
final pCode = await wallet.getPaymentCode(
|
||||
DerivePathTypeExt.primaryFor(manager.coin));
|
||||
|
||||
if (shouldCancel) return;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/paynym/add_new_paynym_follow_view.dart';
|
||||
import 'package:stackwallet/pages/paynym/dialogs/paynym_qr_popup.dart';
|
||||
|
@ -200,7 +201,7 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
|||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
.accentColorDark,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
|
@ -222,7 +223,7 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
|||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
.accentColorDark,
|
||||
),
|
||||
onPressed: () {
|
||||
// todo info ?
|
||||
|
@ -302,7 +303,9 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
|||
.code,
|
||||
12,
|
||||
5),
|
||||
style: STextStyles.label(context),
|
||||
style: STextStyles.label(context).copyWith(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 11,
|
||||
|
@ -312,14 +315,14 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
|||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Copy",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
iconSpacing: 4,
|
||||
buttonHeight: ButtonHeight.xl,
|
||||
iconSpacing: 8,
|
||||
icon: CopyIcon(
|
||||
width: 10,
|
||||
height: 10,
|
||||
width: 12,
|
||||
height: 12,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
.buttonTextSecondary,
|
||||
),
|
||||
onPressed: () async {
|
||||
await Clipboard.setData(
|
||||
|
@ -349,17 +352,34 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
|||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Share",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
iconSpacing: 4,
|
||||
buttonHeight: ButtonHeight.xl,
|
||||
iconSpacing: 8,
|
||||
icon: ShareIcon(
|
||||
width: 10,
|
||||
height: 10,
|
||||
width: 12,
|
||||
height: 12,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
.buttonTextSecondary,
|
||||
),
|
||||
onPressed: () {
|
||||
// copy to clipboard
|
||||
onPressed: () async {
|
||||
Rect? sharePositionOrigin;
|
||||
if (await Util.isIPad) {
|
||||
final box =
|
||||
context.findRenderObject() as RenderBox?;
|
||||
if (box != null) {
|
||||
sharePositionOrigin =
|
||||
box.localToGlobal(Offset.zero) & box.size;
|
||||
}
|
||||
}
|
||||
|
||||
await Share.share(
|
||||
ref
|
||||
.read(myPaynymAccountStateProvider.state)
|
||||
.state!
|
||||
.codes
|
||||
.first
|
||||
.code,
|
||||
sharePositionOrigin: sharePositionOrigin);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -369,14 +389,14 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
|||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Address",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
iconSpacing: 4,
|
||||
buttonHeight: ButtonHeight.xl,
|
||||
iconSpacing: 8,
|
||||
icon: QrCodeIcon(
|
||||
width: 10,
|
||||
height: 10,
|
||||
width: 12,
|
||||
height: 12,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
.buttonTextSecondary,
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog<void>(
|
||||
|
@ -536,7 +556,7 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
|||
child: child,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: isDesktop ? 56 : 40,
|
||||
height: isDesktop ? 56 : 48,
|
||||
width: isDesktop ? 490 : null,
|
||||
child: Toggle(
|
||||
onColor: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
|
|
|
@ -5,14 +5,14 @@ import 'package:flutter/services.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:stackwallet/exceptions/wallet/insufficient_balance_exception.dart';
|
||||
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/paynym/dialogs/confirm_paynym_connect_dialog.dart';
|
||||
import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart';
|
||||
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
|
@ -57,30 +57,24 @@ class _PaynymDetailsPopupState extends ConsumerState<DesktopPaynymDetails> {
|
|||
),
|
||||
);
|
||||
|
||||
final wallet = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as DogecoinWallet;
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(widget.walletId);
|
||||
|
||||
// sanity check to prevent second notification tx
|
||||
if (wallet.hasConnectedConfirmed(widget.accountLite.code)) {
|
||||
canPop = true;
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
// TODO show info popup
|
||||
return;
|
||||
} else if (wallet.hasConnected(widget.accountLite.code)) {
|
||||
final wallet = manager.wallet as PaynymWalletInterface;
|
||||
|
||||
if (await wallet.hasConnected(widget.accountLite.code)) {
|
||||
canPop = true;
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
// TODO show info popup
|
||||
return;
|
||||
}
|
||||
|
||||
final rates = await wallet.fees;
|
||||
final rates = await manager.fees;
|
||||
|
||||
Map<String, dynamic> preparedTx;
|
||||
|
||||
try {
|
||||
preparedTx = await wallet.buildNotificationTx(
|
||||
preparedTx = await wallet.prepareNotificationTx(
|
||||
selectedTxFeeRate: rates.medium,
|
||||
targetPaymentCodeString: widget.accountLite.code,
|
||||
);
|
||||
|
@ -118,7 +112,7 @@ class _PaynymDetailsPopupState extends ConsumerState<DesktopPaynymDetails> {
|
|||
maxHeight: double.infinity,
|
||||
maxWidth: 580,
|
||||
child: ConfirmTransactionView(
|
||||
walletId: wallet.walletId,
|
||||
walletId: manager.walletId,
|
||||
isPaynymNotificationTransaction: true,
|
||||
transactionInfo: {
|
||||
"hex": preparedTx["hex"],
|
||||
|
@ -147,7 +141,7 @@ class _PaynymDetailsPopupState extends ConsumerState<DesktopPaynymDetails> {
|
|||
);
|
||||
},
|
||||
amount: (preparedTx["amount"] as int) + (preparedTx["fee"] as int),
|
||||
coin: wallet.coin,
|
||||
coin: manager.coin,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -159,10 +153,11 @@ class _PaynymDetailsPopupState extends ConsumerState<DesktopPaynymDetails> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final wallet = ref
|
||||
.watch(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as DogecoinWallet;
|
||||
final manager = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId)));
|
||||
|
||||
final wallet = manager.wallet as PaynymWalletInterface;
|
||||
|
||||
return RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Column(
|
||||
|
@ -176,14 +171,47 @@ class _PaynymDetailsPopupState extends ConsumerState<DesktopPaynymDetails> {
|
|||
children: [
|
||||
PayNymBot(
|
||||
paymentCodeString: widget.accountLite.code,
|
||||
size: 32,
|
||||
size: 36,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
widget.accountLite.nymName,
|
||||
style: STextStyles.desktopTextSmall(context),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.accountLite.nymName,
|
||||
style: STextStyles.desktopTextSmall(context),
|
||||
),
|
||||
FutureBuilder(
|
||||
future: wallet.hasConnected(widget.accountLite.code),
|
||||
builder: (context, AsyncSnapshot<bool> snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done &&
|
||||
snapshot.data == true) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Text(
|
||||
"Connected",
|
||||
style: STextStyles.desktopTextSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorGreen,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -192,40 +220,53 @@ class _PaynymDetailsPopupState extends ConsumerState<DesktopPaynymDetails> {
|
|||
),
|
||||
Row(
|
||||
children: [
|
||||
if (!wallet.hasConnected(widget.accountLite.code))
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
label: "Connect",
|
||||
buttonHeight: ButtonHeight.s,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circlePlusFilled,
|
||||
width: 16,
|
||||
height: 16,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextPrimary,
|
||||
),
|
||||
iconSpacing: 6,
|
||||
onPressed: _onConnectPressed,
|
||||
),
|
||||
),
|
||||
if (wallet.hasConnected(widget.accountLite.code))
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
label: "Send",
|
||||
buttonHeight: ButtonHeight.s,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circleArrowUpRight,
|
||||
width: 16,
|
||||
height: 16,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextPrimary,
|
||||
),
|
||||
iconSpacing: 6,
|
||||
onPressed: _onSend,
|
||||
),
|
||||
Expanded(
|
||||
child: FutureBuilder(
|
||||
future: wallet.hasConnected(widget.accountLite.code),
|
||||
builder: (context, AsyncSnapshot<bool> snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
if (snapshot.data!) {
|
||||
return PrimaryButton(
|
||||
label: "Send",
|
||||
buttonHeight: ButtonHeight.s,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circleArrowUpRight,
|
||||
width: 16,
|
||||
height: 16,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextPrimary,
|
||||
),
|
||||
iconSpacing: 6,
|
||||
onPressed: _onSend,
|
||||
);
|
||||
} else {
|
||||
return PrimaryButton(
|
||||
label: "Connect",
|
||||
buttonHeight: ButtonHeight.s,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circlePlusFilled,
|
||||
width: 16,
|
||||
height: 16,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextPrimary,
|
||||
),
|
||||
iconSpacing: 6,
|
||||
onPressed: _onConnectPressed,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const SizedBox(
|
||||
height: 100,
|
||||
child: LoadingIndicator(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
|
@ -316,7 +357,7 @@ class _PaynymDetailsPopupState extends ConsumerState<DesktopPaynymDetails> {
|
|||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Copy",
|
||||
onTap: () async {
|
||||
await Clipboard.setData(
|
||||
|
|
|
@ -37,7 +37,7 @@ class _PaynymCardState extends State<PaynymCard> {
|
|||
child: Row(
|
||||
children: [
|
||||
PayNymBot(
|
||||
size: 32,
|
||||
size: 36,
|
||||
paymentCodeString: widget.paymentCodeString,
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -56,7 +56,7 @@ class _PaynymCardState extends State<PaynymCard> {
|
|||
.extension<StackColors>()!
|
||||
.textFieldActiveText,
|
||||
)
|
||||
: STextStyles.w500_12(context),
|
||||
: STextStyles.w500_14(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
|
@ -65,7 +65,7 @@ class _PaynymCardState extends State<PaynymCard> {
|
|||
Format.shorten(widget.paymentCodeString, 12, 5),
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||
: STextStyles.w500_12(context).copyWith(
|
||||
: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
|
|
|
@ -77,7 +77,7 @@ class _PaynymCardButtonState extends ConsumerState<PaynymCardButton> {
|
|||
child: Row(
|
||||
children: [
|
||||
PayNymBot(
|
||||
size: 32,
|
||||
size: 36,
|
||||
paymentCodeString: widget.accountLite.code,
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -96,7 +96,7 @@ class _PaynymCardButtonState extends ConsumerState<PaynymCardButton> {
|
|||
.extension<StackColors>()!
|
||||
.textFieldActiveText,
|
||||
)
|
||||
: STextStyles.w500_12(context),
|
||||
: STextStyles.w500_14(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
|
@ -105,7 +105,7 @@ class _PaynymCardButtonState extends ConsumerState<PaynymCardButton> {
|
|||
Format.shorten(widget.accountLite.code, 12, 5),
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||
: STextStyles.w500_12(context).copyWith(
|
||||
: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
|
|
|
@ -3,11 +3,17 @@ import 'dart:math';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/pages/paynym/subwidgets/paynym_card_button.dart';
|
||||
import 'package:stackwallet/providers/global/paynym_api_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class PaynymFollowersList extends ConsumerStatefulWidget {
|
||||
|
@ -54,61 +60,97 @@ class _PaynymFollowersListState extends ConsumerState<PaynymFollowersList> {
|
|||
ref.watch(myPaynymAccountStateProvider.state).state?.followers;
|
||||
final count = followers?.length ?? 0;
|
||||
|
||||
return ListView.separated(
|
||||
itemCount: max(count, 1),
|
||||
separatorBuilder: (BuildContext context, int index) => Container(
|
||||
height: 1.5,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
if (count == 0) {
|
||||
return RoundedWhiteContainer(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Your PayNym followers will appear here",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.label(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else if (count == 1) {
|
||||
return RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: PaynymCardButton(
|
||||
walletId: widget.walletId,
|
||||
accountLite: followers![0],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
BorderRadius? borderRadius;
|
||||
if (index == 0) {
|
||||
borderRadius = _borderRadiusFirst;
|
||||
} else if (index == count - 1) {
|
||||
borderRadius = _borderRadiusLast;
|
||||
}
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => RefreshIndicator(
|
||||
child: child,
|
||||
onRefresh: () async {
|
||||
try {
|
||||
final manager = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId);
|
||||
|
||||
return Container(
|
||||
key: Key("paynymCardKey_${followers![index].nymId}"),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: borderRadius,
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
),
|
||||
child: PaynymCardButton(
|
||||
walletId: widget.walletId,
|
||||
accountLite: followers[index],
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
// get wallet to access paynym calls
|
||||
final wallet = manager.wallet as PaynymWalletInterface;
|
||||
|
||||
// get payment code
|
||||
final pCode = await wallet.getPaymentCode(
|
||||
DerivePathTypeExt.primaryFor(manager.coin),
|
||||
);
|
||||
|
||||
// get account from api
|
||||
final account =
|
||||
await ref.read(paynymAPIProvider).nym(pCode.toString());
|
||||
|
||||
// update my account
|
||||
if (account.value != null) {
|
||||
ref.read(myPaynymAccountStateProvider.state).state =
|
||||
account.value!;
|
||||
}
|
||||
} catch (e) {
|
||||
Logging.instance.log(
|
||||
"Failed pull down refresh of paynym home page: $e",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
child: ListView.separated(
|
||||
itemCount: max(count, 1),
|
||||
separatorBuilder: (BuildContext context, int index) => Container(
|
||||
height: 1.5,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
if (count == 0) {
|
||||
return RoundedWhiteContainer(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Your PayNym followers will appear here",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.label(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else if (count == 1) {
|
||||
return RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: PaynymCardButton(
|
||||
walletId: widget.walletId,
|
||||
accountLite: followers![0],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
BorderRadius? borderRadius;
|
||||
if (index == 0) {
|
||||
borderRadius = _borderRadiusFirst;
|
||||
} else if (index == count - 1) {
|
||||
borderRadius = _borderRadiusLast;
|
||||
}
|
||||
|
||||
return Container(
|
||||
key: Key("paynymCardKey_${followers![index].nymId}"),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: borderRadius,
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
),
|
||||
child: PaynymCardButton(
|
||||
walletId: widget.walletId,
|
||||
accountLite: followers[index],
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,17 @@ import 'dart:math';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/pages/paynym/subwidgets/paynym_card_button.dart';
|
||||
import 'package:stackwallet/providers/global/paynym_api_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class PaynymFollowingList extends ConsumerStatefulWidget {
|
||||
|
@ -54,61 +60,97 @@ class _PaynymFollowingListState extends ConsumerState<PaynymFollowingList> {
|
|||
ref.watch(myPaynymAccountStateProvider.state).state?.following;
|
||||
final count = following?.length ?? 0;
|
||||
|
||||
return ListView.separated(
|
||||
itemCount: max(count, 1),
|
||||
separatorBuilder: (BuildContext context, int index) => Container(
|
||||
height: 1.5,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
if (count == 0) {
|
||||
return RoundedWhiteContainer(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Your PayNym contacts will appear here",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.label(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else if (count == 1) {
|
||||
return RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: PaynymCardButton(
|
||||
walletId: widget.walletId,
|
||||
accountLite: following![0],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
BorderRadius? borderRadius;
|
||||
if (index == 0) {
|
||||
borderRadius = _borderRadiusFirst;
|
||||
} else if (index == count - 1) {
|
||||
borderRadius = _borderRadiusLast;
|
||||
}
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => RefreshIndicator(
|
||||
child: child,
|
||||
onRefresh: () async {
|
||||
try {
|
||||
final manager = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId);
|
||||
|
||||
return Container(
|
||||
key: Key("paynymCardKey_${following![index].nymId}"),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: borderRadius,
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
),
|
||||
child: PaynymCardButton(
|
||||
walletId: widget.walletId,
|
||||
accountLite: following[index],
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
// get wallet to access paynym calls
|
||||
final wallet = manager.wallet as PaynymWalletInterface;
|
||||
|
||||
// get payment code
|
||||
final pCode = await wallet.getPaymentCode(
|
||||
DerivePathTypeExt.primaryFor(manager.coin),
|
||||
);
|
||||
|
||||
// get account from api
|
||||
final account =
|
||||
await ref.read(paynymAPIProvider).nym(pCode.toString());
|
||||
|
||||
// update my account
|
||||
if (account.value != null) {
|
||||
ref.read(myPaynymAccountStateProvider.state).state =
|
||||
account.value!;
|
||||
}
|
||||
} catch (e) {
|
||||
Logging.instance.log(
|
||||
"Failed pull down refresh of paynym home page: $e",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
child: ListView.separated(
|
||||
itemCount: max(count, 1),
|
||||
separatorBuilder: (BuildContext context, int index) => Container(
|
||||
height: 1.5,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
if (count == 0) {
|
||||
return RoundedWhiteContainer(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Your PayNym contacts will appear here",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.label(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else if (count == 1) {
|
||||
return RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: PaynymCardButton(
|
||||
walletId: widget.walletId,
|
||||
accountLite: following![0],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
BorderRadius? borderRadius;
|
||||
if (index == 0) {
|
||||
borderRadius = _borderRadiusFirst;
|
||||
} else if (index == count - 1) {
|
||||
borderRadius = _borderRadiusLast;
|
||||
}
|
||||
|
||||
return Container(
|
||||
key: Key("paynymCardKey_${following![index].nymId}"),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: borderRadius,
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
),
|
||||
child: PaynymCardButton(
|
||||
walletId: widget.walletId,
|
||||
accountLite: following[index],
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
132
lib/pages/receive_view/addresses/address_card.dart
Normal file
|
@ -0,0 +1,132 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/address_qr_popup.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/edit_address_label_view.dart';
|
||||
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/icon_widgets/copy_icon.dart';
|
||||
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class AddressCard extends StatelessWidget {
|
||||
const AddressCard({
|
||||
Key? key,
|
||||
required this.address,
|
||||
required this.walletId,
|
||||
required this.coin,
|
||||
this.clipboard = const ClipboardWrapper(),
|
||||
}) : super(key: key);
|
||||
|
||||
final Address address;
|
||||
final String walletId;
|
||||
final Coin coin;
|
||||
final ClipboardInterface clipboard;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoundedWhiteContainer(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"TODO: label",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
CustomTextButton(
|
||||
text: "Edit label",
|
||||
textSize: 14,
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
EditAddressLabelView.routeName,
|
||||
arguments: Tuple2(
|
||||
address,
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SelectableText(
|
||||
address.value,
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Copy address",
|
||||
icon: CopyIcon(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondary,
|
||||
),
|
||||
onPressed: () async {
|
||||
await clipboard.setData(
|
||||
ClipboardData(
|
||||
text: address.value,
|
||||
),
|
||||
);
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Show QR Code",
|
||||
icon: QrCodeIcon(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondary,
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AddressQrPopup(
|
||||
address: address,
|
||||
coin: coin,
|
||||
clipboard: clipboard,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
193
lib/pages/receive_view/addresses/address_qr_popup.dart
Normal file
|
@ -0,0 +1,193 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_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/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/utilities/address_utils.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
|
||||
class AddressQrPopup extends StatefulWidget {
|
||||
const AddressQrPopup({
|
||||
Key? key,
|
||||
required this.address,
|
||||
required this.coin,
|
||||
this.clipboard = const ClipboardWrapper(),
|
||||
}) : super(key: key);
|
||||
|
||||
final Address address;
|
||||
final Coin coin;
|
||||
final ClipboardInterface clipboard;
|
||||
|
||||
@override
|
||||
State<AddressQrPopup> createState() => _AddressQrPopupState();
|
||||
}
|
||||
|
||||
class _AddressQrPopupState extends State<AddressQrPopup> {
|
||||
final _qrKey = GlobalKey();
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
Future<void> _capturePng(bool shouldSaveInsteadOfShare) async {
|
||||
try {
|
||||
RenderRepaintBoundary boundary =
|
||||
_qrKey.currentContext?.findRenderObject() as RenderRepaintBoundary;
|
||||
ui.Image image = await boundary.toImage();
|
||||
ByteData? byteData =
|
||||
await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
Uint8List pngBytes = byteData!.buffer.asUint8List();
|
||||
|
||||
if (shouldSaveInsteadOfShare) {
|
||||
if (isDesktop) {
|
||||
final dir = Directory("${Platform.environment['HOME']}");
|
||||
if (!dir.existsSync()) {
|
||||
throw Exception(
|
||||
"Home dir not found while trying to open filepicker on QR image save");
|
||||
}
|
||||
final path = await FilePicker.platform.saveFile(
|
||||
fileName: "qrcode.png",
|
||||
initialDirectory: dir.path,
|
||||
);
|
||||
|
||||
if (path != null) {
|
||||
final file = File(path);
|
||||
if (file.existsSync()) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "$path already exists!",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await file.writeAsBytes(pngBytes);
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.success,
|
||||
message: "$path saved!",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// await DocumentFileSavePlus.saveFile(
|
||||
// pngBytes,
|
||||
// "receive_qr_code_${DateTime.now().toLocal().toIso8601String()}.png",
|
||||
// "image/png");
|
||||
}
|
||||
} else {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final file = await File("${tempDir.path}/qrcode.png").create();
|
||||
await file.writeAsBytes(pngBytes);
|
||||
|
||||
await Share.shareFiles(["${tempDir.path}/qrcode.png"],
|
||||
text: "Receive URI QR Code");
|
||||
}
|
||||
} catch (e) {
|
||||
//todo: comeback to this
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StackDialogBase(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
"todo: custom label",
|
||||
style: STextStyles.pageTitleH2(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Text(
|
||||
widget.address.value,
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Center(
|
||||
child: RepaintBoundary(
|
||||
key: _qrKey,
|
||||
child: QrImage(
|
||||
data: AddressUtils.buildUriString(
|
||||
widget.coin,
|
||||
widget.address.value,
|
||||
{},
|
||||
),
|
||||
size: 220,
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
foregroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.accentColorDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
width: 170,
|
||||
buttonHeight: isDesktop ? ButtonHeight.l : null,
|
||||
onPressed: () async {
|
||||
await _capturePng(false);
|
||||
},
|
||||
label: "Share",
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.share,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
width: 170,
|
||||
onPressed: () async {
|
||||
await _capturePng(true);
|
||||
},
|
||||
label: "Save",
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.arrowDown,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.buttonTextPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
241
lib/pages/receive_view/addresses/edit_address_label_view.dart
Normal file
|
@ -0,0 +1,241 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/models/isar/models/address/address.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
|
||||
import 'package:stackwallet/widgets/stack_text_field.dart';
|
||||
import 'package:stackwallet/widgets/textfield_icon_button.dart';
|
||||
|
||||
class EditAddressLabelView extends ConsumerStatefulWidget {
|
||||
const EditAddressLabelView({
|
||||
Key? key,
|
||||
required this.address,
|
||||
required this.walletId,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/editAddressLabel";
|
||||
|
||||
final Address address;
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<EditAddressLabelView> createState() =>
|
||||
_EditAddressLabelViewState();
|
||||
}
|
||||
|
||||
class _EditAddressLabelViewState extends ConsumerState<EditAddressLabelView> {
|
||||
late final TextEditingController _labelFieldController;
|
||||
final labelFieldFocusNode = FocusNode();
|
||||
|
||||
late final bool isDesktop;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
isDesktop = Util.isDesktop;
|
||||
_labelFieldController = TextEditingController();
|
||||
_labelFieldController.text = "todo: address.label";
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_labelFieldController.dispose();
|
||||
labelFieldFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Background(
|
||||
child: child,
|
||||
),
|
||||
child: Scaffold(
|
||||
backgroundColor: isDesktop
|
||||
? Colors.transparent
|
||||
: Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: isDesktop
|
||||
? null
|
||||
: AppBar(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 75));
|
||||
}
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"Edit label",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
),
|
||||
body: ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (isDesktop)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
bottom: 12,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Edit label",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: isDesktop
|
||||
? const EdgeInsets.symmetric(
|
||||
horizontal: 32,
|
||||
)
|
||||
: const EdgeInsets.all(0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
autocorrect: Util.isDesktop ? false : true,
|
||||
enableSuggestions: Util.isDesktop ? false : true,
|
||||
controller: _labelFieldController,
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveText,
|
||||
height: 1.8,
|
||||
)
|
||||
: STextStyles.field(context),
|
||||
focusNode: labelFieldFocusNode,
|
||||
decoration: standardInputDecoration(
|
||||
"Address label",
|
||||
labelFieldFocusNode,
|
||||
context,
|
||||
desktopMed: isDesktop,
|
||||
).copyWith(
|
||||
contentPadding: isDesktop
|
||||
? const EdgeInsets.only(
|
||||
left: 16,
|
||||
top: 11,
|
||||
bottom: 12,
|
||||
right: 5,
|
||||
)
|
||||
: null,
|
||||
suffixIcon: _labelFieldController.text.isNotEmpty
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(right: 0),
|
||||
child: UnconstrainedBox(
|
||||
child: Row(
|
||||
children: [
|
||||
TextFieldIconButton(
|
||||
child: const XIcon(),
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
_labelFieldController.text = "";
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// if (!isDesktop)
|
||||
const Spacer(),
|
||||
if (isDesktop)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: PrimaryButton(
|
||||
label: "Save",
|
||||
onPressed: () async {
|
||||
// todo: update address
|
||||
// await ref
|
||||
// .read(notesServiceChangeNotifierProvider(
|
||||
// widget.walletId))
|
||||
// .editOrAddNote(
|
||||
// txid: widget.txid,
|
||||
// note: _labelFieldController.text,
|
||||
// );
|
||||
// if (mounted) {
|
||||
// Navigator.of(context).pop();
|
||||
// }
|
||||
},
|
||||
),
|
||||
),
|
||||
if (!isDesktop)
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
// todo: update address
|
||||
// await ref
|
||||
// .read(
|
||||
// notesServiceChangeNotifierProvider(widget.walletId))
|
||||
// .editOrAddNote(
|
||||
// txid: widget.txid,
|
||||
// note: _labelFieldController.text,
|
||||
// );
|
||||
// if (mounted) {
|
||||
// Navigator.of(context).pop();
|
||||
// }
|
||||
},
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonStyle(context),
|
||||
child: Text(
|
||||
"Save",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/main_db.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/address_card.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/utilities/clipboard_interface.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||
|
||||
class ReceivingAddressesView extends ConsumerWidget {
|
||||
const ReceivingAddressesView({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
required this.isDesktop,
|
||||
this.clipboard = const ClipboardWrapper(),
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/receivingAddressesView";
|
||||
|
||||
final String walletId;
|
||||
final bool isDesktop;
|
||||
final ClipboardInterface clipboard;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final coin = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).coin));
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Background(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.backgroundAppBar,
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"Receiving addresses",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: FutureBuilder(
|
||||
future: MainDB.instance
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(AddressSubType.receiving)
|
||||
.and()
|
||||
.not()
|
||||
.typeEqualTo(AddressType.nonWallet)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findAll(),
|
||||
builder: (context, AsyncSnapshot<List<Address>> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.data != null) {
|
||||
// listview
|
||||
return ListView.separated(
|
||||
itemCount: snapshot.data!.length,
|
||||
separatorBuilder: (_, __) => Container(
|
||||
height: 10,
|
||||
),
|
||||
itemBuilder: (_, index) => AddressCard(
|
||||
walletId: walletId,
|
||||
address: snapshot.data![index],
|
||||
coin: coin,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const Center(
|
||||
child: LoadingIndicator(
|
||||
height: 200,
|
||||
width: 200,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -6,11 +6,13 @@ 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/addresses/receiving_addresses_view.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/clipboard_interface.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/utilities/theme/stack_colors.dart';
|
||||
|
@ -19,6 +21,7 @@ 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';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class ReceiveView extends ConsumerStatefulWidget {
|
||||
const ReceiveView({
|
||||
|
@ -128,6 +131,94 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
"Receive ${coin.ticker}",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: AppBarIconButton(
|
||||
key: const Key("walletNetworkSettingsAddNewNodeViewButton"),
|
||||
size: 36,
|
||||
shadows: const [],
|
||||
color: Theme.of(context).extension<StackColors>()!.background,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.verticalEllipsis,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog<dynamic>(
|
||||
barrierColor: Colors.transparent,
|
||||
barrierDismissible: true,
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 9,
|
||||
right: 10,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.popupBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
// boxShadow: [CFColors.standardBoxShadow],
|
||||
boxShadow: const [],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pushNamed(
|
||||
ReceivingAddressesView.routeName,
|
||||
arguments: Tuple2(walletId, false),
|
||||
);
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
boxShadow: [
|
||||
Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.standardBoxShadow,
|
||||
],
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
child: Text(
|
||||
"Address list",
|
||||
style: STextStyles.field(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
|
@ -233,7 +324,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Create new QR code",
|
||||
onTap: () async {
|
||||
unawaited(Navigator.of(context).push(
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:decimal/decimal.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart';
|
||||
import 'package:stackwallet/pages/send_view/sub_widgets/sending_transaction_dialog.dart';
|
||||
|
@ -12,10 +13,9 @@ import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub
|
|||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
@ -92,11 +92,10 @@ class _ConfirmTransactionViewState
|
|||
try {
|
||||
String txid;
|
||||
if (widget.isPaynymNotificationTransaction) {
|
||||
txid = await (manager.wallet as DogecoinWallet)
|
||||
.confirmNotificationTx(preparedTx: transactionInfo);
|
||||
txid = await (manager.wallet as PaynymWalletInterface)
|
||||
.broadcastNotificationTx(preparedTx: transactionInfo);
|
||||
} else if (widget.isPaynymTransaction) {
|
||||
//
|
||||
throw UnimplementedError("paynym send not implemented yet");
|
||||
txid = await manager.confirmSend(txData: transactionInfo);
|
||||
} else {
|
||||
final coin = manager.coin;
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
|
@ -334,14 +333,20 @@ class _ConfirmTransactionViewState
|
|||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
"Recipient",
|
||||
widget.isPaynymTransaction
|
||||
? "PayNym recipient"
|
||||
: "Recipient",
|
||||
style: STextStyles.smallMed12(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
"${transactionInfo["address"] ?? "ERROR"}",
|
||||
widget.isPaynymTransaction
|
||||
? (transactionInfo["paynymAccountLite"]
|
||||
as PaynymAccountLite)
|
||||
.nymName
|
||||
: "${transactionInfo["address"] ?? "ERROR"}",
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -484,7 +484,7 @@ class AboutView extends ConsumerWidget {
|
|||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "https://stackwallet.com",
|
||||
onTap: () {
|
||||
launchUrl(
|
||||
|
|
|
@ -282,7 +282,7 @@ class _DebugViewState extends ConsumerState<DebugView> {
|
|||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Save Debug Info to clipboard",
|
||||
onTap: () async {
|
||||
try {
|
||||
|
@ -350,7 +350,7 @@ class _DebugViewState extends ConsumerState<DebugView> {
|
|||
},
|
||||
),
|
||||
const Spacer(),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Save logs to file",
|
||||
onTap: () async {
|
||||
final systemfile = SWBFileSystem();
|
||||
|
|
|
@ -92,7 +92,7 @@ class _CoinNodesViewState extends ConsumerState<CoinNodesView> {
|
|||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Add new node",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
|
|
|
@ -327,7 +327,7 @@ class _AutoBackupViewState extends ConsumerState<AutoBackupView> {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Back up now",
|
||||
onTap: () {
|
||||
ref.read(autoSWBServiceProvider).doBackup();
|
||||
|
@ -448,7 +448,7 @@ class _AutoBackupViewState extends ConsumerState<AutoBackupView> {
|
|||
height: 20,
|
||||
),
|
||||
Center(
|
||||
child: BlueTextButton(
|
||||
child: CustomTextButton(
|
||||
text: "Edit Auto Backup",
|
||||
onTap: () async {
|
||||
Navigator.of(context)
|
||||
|
|
|
@ -2,6 +2,9 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||
import 'package:stackwallet/widgets/stack_dialog.dart';
|
||||
|
||||
class RescanningDialog extends StatefulWidget {
|
||||
|
@ -21,9 +24,12 @@ class _RescanningDialogState extends State<RescanningDialog>
|
|||
late AnimationController? _spinController;
|
||||
late Animation<double> _spinAnimation;
|
||||
|
||||
late final bool isDesktop;
|
||||
|
||||
// late final VoidCallback onCancel;
|
||||
@override
|
||||
void initState() {
|
||||
isDesktop = Util.isDesktop;
|
||||
// onCancel = widget.onCancel;
|
||||
|
||||
_spinController = AnimationController(
|
||||
|
@ -53,33 +59,42 @@ class _RescanningDialogState extends State<RescanningDialog>
|
|||
onWillPop: () async {
|
||||
return false;
|
||||
},
|
||||
child: StackDialog(
|
||||
title: "Rescanning blockchain",
|
||||
message: "This may take a while. Please do not exit this screen.",
|
||||
icon: RotationTransition(
|
||||
turns: _spinAnimation,
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.arrowRotate3,
|
||||
width: 24,
|
||||
height: 24,
|
||||
color: Theme.of(context).extension<StackColors>()!.accentColorDark,
|
||||
),
|
||||
child: ConditionalParent(
|
||||
condition: isDesktop,
|
||||
builder: (child) => DesktopDialog(
|
||||
maxHeight: 200,
|
||||
maxWidth: 500,
|
||||
child: child,
|
||||
),
|
||||
child: StackDialog(
|
||||
title: "Rescanning blockchain",
|
||||
message: "This may take a while. Please do not exit this screen.",
|
||||
icon: RotationTransition(
|
||||
turns: _spinAnimation,
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.arrowRotate3,
|
||||
width: 24,
|
||||
height: 24,
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.accentColorDark,
|
||||
),
|
||||
),
|
||||
// rightButton: TextButton(
|
||||
// style: Theme.of(context).textButtonTheme.style?.copyWith(
|
||||
// backgroundColor: MaterialStateProperty.all<Color>(
|
||||
// CFColors.buttonGray,
|
||||
// ),
|
||||
// ),
|
||||
// child: Text(
|
||||
// "Cancel",
|
||||
// style: STextStyles.itemSubtitle12(context),
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// Navigator.of(context).pop();
|
||||
// onCancel.call();
|
||||
// },
|
||||
// ),
|
||||
),
|
||||
// rightButton: TextButton(
|
||||
// style: Theme.of(context).textButtonTheme.style?.copyWith(
|
||||
// backgroundColor: MaterialStateProperty.all<Color>(
|
||||
// CFColors.buttonGray,
|
||||
// ),
|
||||
// ),
|
||||
// child: Text(
|
||||
// "Cancel",
|
||||
// style: STextStyles.itemSubtitle12(context),
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// Navigator.of(context).pop();
|
||||
// onCancel.call();
|
||||
// },
|
||||
// ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -76,6 +76,8 @@ class _WalletNetworkSettingsViewState
|
|||
StreamSubscription<dynamic>? _blocksRemainingSubscription;
|
||||
// late StreamSubscription _nodeStatusSubscription;
|
||||
|
||||
late final bool isDesktop;
|
||||
|
||||
late double _percent;
|
||||
late int _blocksRemaining;
|
||||
bool _advancedIsExpanded = false;
|
||||
|
@ -114,7 +116,7 @@ class _WalletNetworkSettingsViewState
|
|||
|
||||
if (mounted) {
|
||||
// pop rescanning dialog
|
||||
Navigator.pop(context);
|
||||
Navigator.of(context, rootNavigator: isDesktop).pop();
|
||||
|
||||
// show success
|
||||
await showDialog<dynamic>(
|
||||
|
@ -132,7 +134,7 @@ class _WalletNetworkSettingsViewState
|
|||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context, rootNavigator: isDesktop).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -143,7 +145,7 @@ class _WalletNetworkSettingsViewState
|
|||
|
||||
if (mounted) {
|
||||
// pop rescanning dialog
|
||||
Navigator.pop(context);
|
||||
Navigator.of(context, rootNavigator: isDesktop).pop();
|
||||
|
||||
// show error
|
||||
await showDialog<dynamic>(
|
||||
|
@ -162,7 +164,7 @@ class _WalletNetworkSettingsViewState
|
|||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context, rootNavigator: isDesktop).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -183,6 +185,7 @@ class _WalletNetworkSettingsViewState
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
isDesktop = Util.isDesktop;
|
||||
_currentSyncStatus = widget.initialSyncStatus;
|
||||
// _currentNodeStatus = widget.initialNodeStatus;
|
||||
if (_currentSyncStatus == WalletSyncStatus.synced) {
|
||||
|
@ -270,7 +273,6 @@ class _WalletNetworkSettingsViewState
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final bool isDesktop = Util.isDesktop;
|
||||
|
||||
final progressLength = isDesktop
|
||||
? 430.0
|
||||
|
@ -747,7 +749,7 @@ class _WalletNetworkSettingsViewState
|
|||
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||
: STextStyles.smallMed12(context),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Add new node",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
|
@ -878,7 +880,7 @@ class _WalletNetworkSettingsViewState
|
|||
top: 16,
|
||||
bottom: 6,
|
||||
),
|
||||
child: BlueTextButton(
|
||||
child: CustomTextButton(
|
||||
text: "Rescan",
|
||||
onTap: () async {
|
||||
await Navigator.of(context).push(
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
|
||||
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
|
||||
import 'package:stackwallet/providers/global/trades_service_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
|
@ -21,6 +20,8 @@ import 'package:stackwallet/widgets/trade_card.dart';
|
|||
import 'package:stackwallet/widgets/transaction_card.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../../utilities/enums/coin_enum.dart';
|
||||
|
||||
class TransactionsList extends ConsumerStatefulWidget {
|
||||
const TransactionsList({
|
||||
Key? key,
|
||||
|
@ -68,11 +69,18 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
BuildContext context,
|
||||
Transaction tx,
|
||||
BorderRadius? radius,
|
||||
Coin coin,
|
||||
) {
|
||||
final matchingTrades = ref
|
||||
.read(tradesServiceProvider)
|
||||
.trades
|
||||
.where((e) => e.payInTxid == tx.txid || e.payOutTxid == tx.txid);
|
||||
|
||||
final isConfirmed = tx.isConfirmed(
|
||||
ref.watch(
|
||||
widget.managerProvider.select((value) => value.currentHeight)),
|
||||
coin.requiredConfirmations);
|
||||
|
||||
if (tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) {
|
||||
final trade = matchingTrades.first;
|
||||
return Container(
|
||||
|
@ -85,7 +93,9 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
children: [
|
||||
TransactionCard(
|
||||
// this may mess with combined firo transactions
|
||||
key: Key(tx.txid + tx.type.name + tx.address.value.toString()), //
|
||||
key: isConfirmed
|
||||
? Key(tx.txid + tx.type.name + tx.address.value.toString())
|
||||
: UniqueKey(), //
|
||||
transaction: tx,
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
|
@ -180,7 +190,9 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
),
|
||||
child: TransactionCard(
|
||||
// this may mess with combined firo transactions
|
||||
key: Key(tx.txid + tx.type.name + tx.address.value.toString()), //
|
||||
key: isConfirmed
|
||||
? Key(tx.txid + tx.type.name + tx.address.value.toString())
|
||||
: UniqueKey(),
|
||||
transaction: tx,
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
|
@ -188,13 +200,6 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
}
|
||||
}
|
||||
|
||||
void updateHeightProvider(Manager manager) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
ref.read(currentHeightProvider(manager.coin).state).state =
|
||||
manager.currentHeight;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
managerProvider = widget.managerProvider;
|
||||
|
@ -203,14 +208,9 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// final managerProvider = ref
|
||||
// .watch(walletsChangeNotifierProvider)
|
||||
// .getManagerProvider(widget.walletId);
|
||||
|
||||
final manager = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(widget.walletId)));
|
||||
|
||||
updateHeightProvider(manager);
|
||||
return FutureBuilder(
|
||||
future: manager.transactions,
|
||||
builder: (fbContext, AsyncSnapshot<List<Transaction>> snapshot) {
|
||||
|
@ -264,7 +264,7 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
radius = _borderRadiusFirst;
|
||||
}
|
||||
final tx = _transactions2[index];
|
||||
return itemBuilder(context, tx, radius);
|
||||
return itemBuilder(context, tx, radius, manager.coin);
|
||||
},
|
||||
separatorBuilder: (context, index) {
|
||||
return Container(
|
||||
|
@ -291,7 +291,7 @@ class _TransactionsListState extends ConsumerState<TransactionsList> {
|
|||
radius = _borderRadiusFirst;
|
||||
}
|
||||
final tx = _transactions2[index];
|
||||
return itemBuilder(context, tx, radius);
|
||||
return itemBuilder(context, tx, radius, manager.coin);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
@ -8,16 +8,16 @@ import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
|
|||
import 'package:stackwallet/providers/global/paynym_api_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart';
|
||||
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/loading_indicator.dart';
|
||||
|
||||
class WalletNavigationBar extends StatefulWidget {
|
||||
class WalletNavigationBar extends ConsumerStatefulWidget {
|
||||
const WalletNavigationBar({
|
||||
Key? key,
|
||||
required this.onReceivePressed,
|
||||
|
@ -40,10 +40,11 @@ class WalletNavigationBar extends StatefulWidget {
|
|||
final String walletId;
|
||||
|
||||
@override
|
||||
State<WalletNavigationBar> createState() => _WalletNavigationBarState();
|
||||
ConsumerState<WalletNavigationBar> createState() =>
|
||||
_WalletNavigationBarState();
|
||||
}
|
||||
|
||||
class _WalletNavigationBarState extends State<WalletNavigationBar> {
|
||||
class _WalletNavigationBarState extends ConsumerState<WalletNavigationBar> {
|
||||
double scale = 0;
|
||||
final duration = const Duration(milliseconds: 200);
|
||||
|
||||
|
@ -61,41 +62,41 @@ class _WalletNavigationBarState extends State<WalletNavigationBar> {
|
|||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AnimatedOpacity(
|
||||
opacity: scale,
|
||||
duration: duration,
|
||||
child: GestureDetector(
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
width: 146,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
boxShadow: [
|
||||
Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.standardBoxShadow
|
||||
],
|
||||
borderRadius: BorderRadius.circular(
|
||||
widget.height / 2.0,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Whirlpool",
|
||||
style: STextStyles.w600_12(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
// AnimatedOpacity(
|
||||
// opacity: scale,
|
||||
// duration: duration,
|
||||
// child: GestureDetector(
|
||||
// onTap: () {},
|
||||
// child: Container(
|
||||
// padding: const EdgeInsets.all(16),
|
||||
// width: 146,
|
||||
// decoration: BoxDecoration(
|
||||
// color:
|
||||
// Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
// boxShadow: [
|
||||
// Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .standardBoxShadow
|
||||
// ],
|
||||
// borderRadius: BorderRadius.circular(
|
||||
// widget.height / 2.0,
|
||||
// ),
|
||||
// ),
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// Text(
|
||||
// "Whirlpool",
|
||||
// style: STextStyles.w600_12(context),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// height: 8,
|
||||
// ),
|
||||
AnimatedOpacity(
|
||||
opacity: scale,
|
||||
duration: duration,
|
||||
|
@ -114,13 +115,15 @@ class _WalletNavigationBarState extends State<WalletNavigationBar> {
|
|||
),
|
||||
);
|
||||
|
||||
// todo make generic and not doge specific
|
||||
final wallet = (ref
|
||||
final manager = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as DogecoinWallet);
|
||||
.getManager(widget.walletId);
|
||||
|
||||
final code = await wallet.getPaymentCode();
|
||||
final paynymInterface =
|
||||
manager.wallet as PaynymWalletInterface;
|
||||
|
||||
final code = await paynymInterface.getPaymentCode(
|
||||
DerivePathTypeExt.primaryFor(manager.coin));
|
||||
|
||||
final account = await ref
|
||||
.read(paynymAPIProvider)
|
||||
|
@ -172,7 +175,18 @@ class _WalletNavigationBarState extends State<WalletNavigationBar> {
|
|||
children: [
|
||||
Text(
|
||||
"Paynym",
|
||||
style: STextStyles.w600_12(context),
|
||||
style: STextStyles.buttonSmall(context),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.robotHead,
|
||||
height: 20,
|
||||
width: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.bottomNavIconIcon,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -357,61 +371,6 @@ class _WalletNavigationBarState extends State<WalletNavigationBar> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (widget.coin.hasPaynymSupport)
|
||||
RawMaterialButton(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 66,
|
||||
),
|
||||
onPressed: () {
|
||||
if (scale == 0) {
|
||||
setState(() {
|
||||
scale = 1;
|
||||
});
|
||||
} else if (scale == 1) {
|
||||
setState(() {
|
||||
scale = 0;
|
||||
});
|
||||
}
|
||||
},
|
||||
splashColor:
|
||||
Theme.of(context).extension<StackColors>()!.highlight,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
widget.height / 2.0,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.bars,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
Text(
|
||||
"More",
|
||||
style: STextStyles.buttonSmall(context),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
if (widget.coin.hasBuySupport)
|
||||
RawMaterialButton(
|
||||
constraints: const BoxConstraints(
|
||||
|
@ -451,10 +410,65 @@ class _WalletNavigationBarState extends State<WalletNavigationBar> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (widget.coin.hasBuySupport)
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
if (ref.watch(walletsChangeNotifierProvider.select((value) =>
|
||||
value.getManager(widget.walletId).hasPaynymSupport)))
|
||||
RawMaterialButton(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 66,
|
||||
),
|
||||
onPressed: () {
|
||||
if (scale == 0) {
|
||||
setState(() {
|
||||
scale = 1;
|
||||
});
|
||||
} else if (scale == 1) {
|
||||
setState(() {
|
||||
scale = 0;
|
||||
});
|
||||
}
|
||||
},
|
||||
splashColor:
|
||||
Theme.of(context).extension<StackColors>()!.highlight,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
widget.height / 2.0,
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.bars,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.bottomNavIconIcon,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
Text(
|
||||
"More",
|
||||
style: STextStyles.buttonSmall(context),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -10,7 +10,6 @@ import 'package:stackwallet/notifications/show_flush_bar.dart';
|
|||
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_search_filter_view.dart';
|
||||
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
|
||||
import 'package:stackwallet/providers/global/address_book_service_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
||||
|
@ -131,7 +130,9 @@ class _TransactionDetailsViewState extends ConsumerState<AllTransactionsView> {
|
|||
// check if address book name contains
|
||||
contains |= contacts
|
||||
.where((e) =>
|
||||
e.addresses.where((a) => a.address == tx.address).isNotEmpty &&
|
||||
e.addresses
|
||||
.where((a) => a.address == tx.address.value?.value)
|
||||
.isNotEmpty &&
|
||||
e.name.toLowerCase().contains(keyword))
|
||||
.isNotEmpty;
|
||||
|
||||
|
@ -853,7 +854,8 @@ class _DesktopTransactionCardRowState
|
|||
prefix = "";
|
||||
}
|
||||
|
||||
final currentHeight = ref.watch(currentHeightProvider(coin).state).state;
|
||||
final currentHeight = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).currentHeight));
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
|
|
|
@ -11,7 +11,6 @@ import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
|||
import 'package:stackwallet/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
||||
import 'package:stackwallet/providers/blockchain/dogecoin/current_height_provider.dart';
|
||||
import 'package:stackwallet/providers/global/address_book_service_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
||||
|
@ -298,7 +297,8 @@ class _TransactionDetailsViewState
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentHeight = ref.watch(currentHeightProvider(coin).state).state;
|
||||
final currentHeight = ref.watch(walletsChangeNotifierProvider
|
||||
.select((value) => value.getManager(walletId).currentHeight));
|
||||
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
|
@ -1092,7 +1092,7 @@ class _TransactionDetailsViewState
|
|||
height: 8,
|
||||
),
|
||||
if (coin != Coin.epicCash)
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Open in block explorer",
|
||||
onTap: () async {
|
||||
final uri =
|
||||
|
|
|
@ -649,7 +649,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
.textDark3,
|
||||
),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "See all",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
|
|
|
@ -24,7 +24,7 @@ class AllWallets extends StatelessWidget {
|
|||
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||
),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Add new",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(AddWalletView.routeName);
|
||||
|
|
|
@ -66,7 +66,7 @@ class DesktopAddressCard extends StatelessWidget {
|
|||
),
|
||||
Row(
|
||||
children: [
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Copy",
|
||||
onTap: () {
|
||||
clipboard.setData(
|
||||
|
@ -87,7 +87,7 @@ class DesktopAddressCard extends StatelessWidget {
|
|||
if (contactId != "default")
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
return BlueTextButton(
|
||||
return CustomTextButton(
|
||||
text: "Edit",
|
||||
onTap: () async {
|
||||
ref.refresh(
|
||||
|
|
|
@ -182,7 +182,7 @@ class _DesktopContactDetailsState extends ConsumerState<DesktopContactDetails> {
|
|||
style:
|
||||
STextStyles.desktopTextExtraExtraSmall(context),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Add new",
|
||||
onTap: () async {
|
||||
ref.refresh(
|
||||
|
|
|
@ -300,8 +300,8 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
|
|||
),
|
||||
if (isStackCoin(ref.watch(desktopExchangeModelProvider
|
||||
.select((value) => value!.receiveTicker))))
|
||||
BlueTextButton(
|
||||
text: "Choose from stack",
|
||||
CustomTextButton(
|
||||
text: "Choose from Stack",
|
||||
onTap: selectRecipientAddressFromStack,
|
||||
),
|
||||
],
|
||||
|
@ -432,8 +432,8 @@ class _DesktopStep2State extends ConsumerState<DesktopStep2> {
|
|||
),
|
||||
if (isStackCoin(ref.watch(desktopExchangeModelProvider
|
||||
.select((value) => value!.sendTicker))))
|
||||
BlueTextButton(
|
||||
text: "Choose from stack",
|
||||
CustomTextButton(
|
||||
text: "Choose from Stack",
|
||||
onTap: selectRefundAddressFromStack,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -221,7 +221,7 @@ class _DesktopChooseFromStackState
|
|||
const SizedBox(
|
||||
width: 80,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Select wallet",
|
||||
onTap: () async {
|
||||
final address =
|
||||
|
|
|
@ -72,7 +72,7 @@ class _DesktopTradeHistoryState extends ConsumerState<DesktopTradeHistory> {
|
|||
"Recent trades",
|
||||
style: STextStyles.desktopTextExtraExtraSmall(context),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "See all",
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
|
|
|
@ -39,7 +39,7 @@ class DesktopFavoriteWallets extends ConsumerWidget {
|
|||
.textFieldActiveSearchIconRight,
|
||||
),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Edit",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(ManageFavoritesView.routeName);
|
||||
|
|
|
@ -38,7 +38,7 @@ class _MyWalletsState extends ConsumerState<MyWallets> {
|
|||
),
|
||||
),
|
||||
const Spacer(),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Add new wallet",
|
||||
onTap: () {
|
||||
Navigator.of(
|
||||
|
|
|
@ -46,10 +46,15 @@ class _WalletTableState extends ConsumerState<WalletSummaryTable> {
|
|||
|
||||
VoidCallback? expandOverride;
|
||||
if (providers.length == 1) {
|
||||
expandOverride = () {
|
||||
Navigator.of(context).pushNamed(
|
||||
expandOverride = () async {
|
||||
final manager = ref.read(providers.first);
|
||||
if (manager.coin == Coin.monero ||
|
||||
manager.coin == Coin.wownero) {
|
||||
await manager.initializeExisting();
|
||||
}
|
||||
await Navigator.of(context).pushNamed(
|
||||
DesktopWalletView.routeName,
|
||||
arguments: ref.read(providers.first).walletId,
|
||||
arguments: manager.walletId,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -19,14 +19,14 @@ import 'package:stackwallet/providers/global/paynym_api_provider.dart';
|
|||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart';
|
||||
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
|
@ -209,13 +209,13 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
|||
),
|
||||
);
|
||||
|
||||
// todo make generic and not doge specific
|
||||
final wallet = (ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as DogecoinWallet);
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(widget.walletId);
|
||||
|
||||
final code = await wallet.getPaymentCode();
|
||||
final wallet = manager.wallet as PaynymWalletInterface;
|
||||
|
||||
final code =
|
||||
await wallet.getPaymentCode(DerivePathTypeExt.primaryFor(manager.coin));
|
||||
|
||||
final account = await ref.read(paynymAPIProvider).nym(code.toString());
|
||||
|
||||
|
@ -445,7 +445,8 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (coin.hasPaynymSupport)
|
||||
if (ref.watch(walletsChangeNotifierProvider.select((value) =>
|
||||
value.getManager(widget.walletId).hasPaynymSupport)))
|
||||
SecondaryButton(
|
||||
label: "PayNym",
|
||||
width: 160,
|
||||
|
|
|
@ -133,7 +133,7 @@ class _ContactListItemState extends ConsumerState<ContactListItem> {
|
|||
],
|
||||
),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Select wallet",
|
||||
onTap: () {
|
||||
Navigator.of(context).pop(e);
|
||||
|
|
|
@ -987,7 +987,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Send all ${coin.ticker}",
|
||||
onTap: sendAllTapped,
|
||||
),
|
||||
|
|
|
@ -37,7 +37,7 @@ class _RecentDesktopTransactionsState
|
|||
.textFieldActiveSearchIconLeft,
|
||||
),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "See all",
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
|
|
|
@ -261,7 +261,7 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
|
|||
const SizedBox(
|
||||
height: 60,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Forgot password?",
|
||||
textSize: 20,
|
||||
onTap: () {
|
||||
|
|
|
@ -450,7 +450,7 @@ class _BackupRestoreSettings extends ConsumerState<BackupRestoreSettings> {
|
|||
STextStyles.itemSubtitle(
|
||||
context),
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text: "Back up now",
|
||||
onTap: () {
|
||||
ref
|
||||
|
|
|
@ -678,7 +678,7 @@ class DesktopAboutView extends ConsumerWidget {
|
|||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
BlueTextButton(
|
||||
CustomTextButton(
|
||||
text:
|
||||
"https://stackwallet.com",
|
||||
onTap: () {
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
import 'package:dart_numerics/dart_numerics.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
||||
final currentHeightProvider =
|
||||
StateProvider.family<int, Coin>((ref, coin) => int64MaxValue);
|
|
@ -6,6 +6,8 @@ import 'package:stackwallet/models/buy/response_objects/quote.dart';
|
|||
import 'package:stackwallet/models/contact_address_entry.dart';
|
||||
import 'package:stackwallet/models/exchange/incomplete_exchange.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
||||
import 'package:stackwallet/models/isar/models/address/address.dart';
|
||||
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
|
||||
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart';
|
||||
import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart';
|
||||
|
@ -43,6 +45,8 @@ import 'package:stackwallet/pages/paynym/add_new_paynym_follow_view.dart';
|
|||
import 'package:stackwallet/pages/paynym/paynym_claim_view.dart';
|
||||
import 'package:stackwallet/pages/paynym/paynym_home_view.dart';
|
||||
import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/edit_address_label_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/receiving_addresses_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/receive_view.dart';
|
||||
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
|
||||
|
@ -472,6 +476,21 @@ class RouteGenerator {
|
|||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case EditAddressLabelView.routeName:
|
||||
if (args is Tuple2<Address, String>) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => EditAddressLabelView(
|
||||
address: args.item1,
|
||||
walletId: args.item2,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case EditTradeNoteView.routeName:
|
||||
if (args is Tuple2<String, String>) {
|
||||
return getRoute(
|
||||
|
@ -819,6 +838,21 @@ class RouteGenerator {
|
|||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case ReceivingAddressesView.routeName:
|
||||
if (args is Tuple2<String, bool>) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => ReceivingAddressesView(
|
||||
walletId: args.item1,
|
||||
isDesktop: args.item2,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case SendView.routeName:
|
||||
if (args is Tuple2<String, Coin>) {
|
||||
return getRoute(
|
||||
|
@ -843,6 +877,18 @@ class RouteGenerator {
|
|||
name: settings.name,
|
||||
),
|
||||
);
|
||||
} else if (args is Tuple3<String, Coin, PaynymAccountLite>) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => SendView(
|
||||
walletId: args.item1,
|
||||
coin: args.item2,
|
||||
accountLite: args.item3,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
enum BuyExceptionType { generic, serializeResponseError }
|
||||
enum BuyExceptionType {
|
||||
generic,
|
||||
serializeResponseError,
|
||||
cryptoAmountOutOfRange,
|
||||
}
|
||||
|
||||
class BuyException implements Exception {
|
||||
String errorMessage;
|
||||
|
|
|
@ -210,7 +210,20 @@ class SimplexAPI {
|
|||
|
||||
BuyResponse<SimplexQuote> _parseQuote(dynamic jsonArray) {
|
||||
try {
|
||||
String cryptoAmount = "${jsonArray['digital_money']['amount']}";
|
||||
// final Map<String, dynamic> lol =
|
||||
// Map<String, dynamic>.from(jsonArray as Map);
|
||||
|
||||
double? cryptoAmount = jsonArray['digital_money']?['amount'] as double?;
|
||||
|
||||
if (cryptoAmount == null) {
|
||||
String error = jsonArray['error'] as String;
|
||||
return BuyResponse(
|
||||
exception: BuyException(
|
||||
error,
|
||||
BuyExceptionType.cryptoAmountOutOfRange,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SimplexQuote quote = jsonArray['quote'] as SimplexQuote;
|
||||
final SimplexQuote _quote = SimplexQuote(
|
||||
|
@ -277,9 +290,9 @@ class SimplexAPI {
|
|||
}
|
||||
final jsonArray = jsonDecode(res.body); // TODO check if valid json
|
||||
if (jsonArray.containsKey('error') as bool) {
|
||||
if (jsonArray['error'] == true || jsonArray['error'] == 'true') {
|
||||
throw Exception(jsonArray['message']);
|
||||
}
|
||||
if (jsonArray['error'] == true || jsonArray['error'] == 'true') {
|
||||
throw Exception(jsonArray['message']);
|
||||
}
|
||||
}
|
||||
|
||||
SimplexOrder _order = SimplexOrder(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:bech32/bech32.dart';
|
||||
import 'package:bip32/bip32.dart' as bip32;
|
||||
|
@ -13,16 +14,18 @@ import 'package:isar/isar.dart';
|
|||
import 'package:stackwallet/db/main_db.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||
import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart';
|
||||
import 'package:stackwallet/models/balance.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||
import 'package:stackwallet/services/coins/coin_service.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/services/mixins/electrum_x_parsing.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
||||
import 'package:stackwallet/services/node_service.dart';
|
||||
|
@ -33,10 +36,12 @@ import 'package:stackwallet/utilities/assets.dart';
|
|||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/paynym_is_api.dart';
|
||||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
@ -50,8 +55,6 @@ const String GENESIS_HASH_MAINNET =
|
|||
const String GENESIS_HASH_TESTNET =
|
||||
"000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943";
|
||||
|
||||
enum DerivePathType { bip44, bip49, bip84 }
|
||||
|
||||
bip32.BIP32 getBip32Node(
|
||||
int chain,
|
||||
int index,
|
||||
|
@ -103,7 +106,7 @@ bip32.BIP32 getBip32NodeFromRoot(
|
|||
case DerivePathType.bip84:
|
||||
return root.derivePath("m/84'/$coinType'/0'/$chain/$index");
|
||||
default:
|
||||
throw Exception("DerivePathType must not be null.");
|
||||
throw Exception("DerivePathType $derivePathType not supported");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,7 +141,8 @@ bip32.BIP32 getBip32RootWrapper(Tuple2<String, NetworkType> args) {
|
|||
return getBip32Root(args.item1, args.item2);
|
||||
}
|
||||
|
||||
class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
||||
class BitcoinWallet extends CoinServiceAPI
|
||||
with WalletCache, WalletDB, ElectrumXParsing, PaynymWalletInterface {
|
||||
static const integrationTestFlag =
|
||||
bool.fromEnvironment("IS_INTEGRATION_TEST");
|
||||
|
||||
|
@ -178,8 +182,16 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
Future<List<isar_models.UTXO>> get utxos => db.getUTXOs(walletId).findAll();
|
||||
|
||||
@override
|
||||
Future<List<isar_models.Transaction>> get transactions =>
|
||||
db.getTransactions(walletId).sortByTimestampDesc().findAll();
|
||||
Future<List<isar_models.Transaction>> get transactions => db
|
||||
.getTransactions(walletId)
|
||||
.filter()
|
||||
.not()
|
||||
.group((q) => q
|
||||
.subTypeEqualTo(isar_models.TransactionSubType.bip47Notification)
|
||||
.and()
|
||||
.typeEqualTo(isar_models.TransactionType.incoming))
|
||||
.sortByTimestampDesc()
|
||||
.findAll();
|
||||
|
||||
@override
|
||||
Future<String> get currentReceivingAddress async =>
|
||||
|
@ -193,7 +205,7 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.subTypeEqualTo(isar_models.AddressSubType.receiving)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()) ??
|
||||
await _generateAddressForChain(0, 0, DerivePathType.bip84);
|
||||
await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
Future<String> get currentChangeAddress async =>
|
||||
(await _currentChangeAddress).value;
|
||||
|
@ -206,7 +218,20 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.subTypeEqualTo(isar_models.AddressSubType.change)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()) ??
|
||||
await _generateAddressForChain(1, 0, DerivePathType.bip84);
|
||||
await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
Future<String> get currentChangeAddressP2PKH async =>
|
||||
(await _currentChangeAddressP2PKH).value;
|
||||
|
||||
Future<isar_models.Address> get _currentChangeAddressP2PKH async =>
|
||||
(await db
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.typeEqualTo(isar_models.AddressType.p2pkh)
|
||||
.subTypeEqualTo(isar_models.AddressSubType.change)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()) ??
|
||||
await _generateAddressForChain(1, 0, DerivePathType.bip44);
|
||||
|
||||
@override
|
||||
Future<void> exit() async {
|
||||
|
@ -241,6 +266,14 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
@ -403,7 +436,7 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
addrType = isar_models.AddressType.p2wpkh;
|
||||
break;
|
||||
default:
|
||||
throw Exception("No Path type $type exists");
|
||||
throw Exception("DerivePathType $type not supported");
|
||||
}
|
||||
|
||||
final address = isar_models.Address(
|
||||
|
@ -491,6 +524,7 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
required String mnemonic,
|
||||
int maxUnusedAddressGap = 20,
|
||||
int maxNumberOfIndexesToCheck = 1000,
|
||||
bool isRescan = false,
|
||||
}) async {
|
||||
longMutex = true;
|
||||
|
||||
|
@ -664,14 +698,49 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
p2wpkhChangeAddressArray.add(address);
|
||||
}
|
||||
|
||||
await db.putAddresses([
|
||||
...p2wpkhReceiveAddressArray,
|
||||
...p2wpkhChangeAddressArray,
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
...p2shReceiveAddressArray,
|
||||
...p2shChangeAddressArray,
|
||||
]);
|
||||
if (isRescan) {
|
||||
await db.updateOrPutAddresses([
|
||||
...p2wpkhReceiveAddressArray,
|
||||
...p2wpkhChangeAddressArray,
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
...p2shReceiveAddressArray,
|
||||
...p2shChangeAddressArray,
|
||||
]);
|
||||
} else {
|
||||
await db.putAddresses([
|
||||
...p2wpkhReceiveAddressArray,
|
||||
...p2wpkhChangeAddressArray,
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
...p2shReceiveAddressArray,
|
||||
...p2shChangeAddressArray,
|
||||
]);
|
||||
}
|
||||
|
||||
// get own payment code
|
||||
final myCode = await getPaymentCode(DerivePathType.bip44);
|
||||
|
||||
// refresh transactions to pick up any received notification transactions
|
||||
await _refreshTransactions();
|
||||
|
||||
final Set<String> codesToCheck = {};
|
||||
final nym = await PaynymIsApi().nym(myCode.toString());
|
||||
if (nym.value != null) {
|
||||
for (final follower in nym.value!.followers) {
|
||||
codesToCheck.add(follower.code);
|
||||
}
|
||||
for (final following in nym.value!.following) {
|
||||
codesToCheck.add(following.code);
|
||||
}
|
||||
}
|
||||
|
||||
// restore paynym transactions
|
||||
await restoreAllHistory(
|
||||
maxUnusedAddressGap: maxUnusedAddressGap,
|
||||
maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
|
||||
paymentCodeStrings: codesToCheck,
|
||||
);
|
||||
|
||||
await _updateUTXOs();
|
||||
|
||||
|
@ -737,6 +806,13 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
}
|
||||
return needsRefresh;
|
||||
} on NoSuchTransactionException catch (e) {
|
||||
// TODO: move direct transactions elsewhere
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.transactions.deleteByTxidWalletId(e.txid, walletId);
|
||||
});
|
||||
await txTracker.deleteTransaction(e.txid);
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception caught in refreshIfThereIsNewData: $e\n$s",
|
||||
|
@ -890,6 +966,17 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId));
|
||||
|
||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId));
|
||||
final myCode = await getPaymentCode(DerivePathType.bip44);
|
||||
final Set<String> codesToCheck = {};
|
||||
final nym = await PaynymIsApi().nym(myCode.toString());
|
||||
if (nym.value != null) {
|
||||
for (final follower in nym.value!.followers) {
|
||||
codesToCheck.add(follower.code);
|
||||
}
|
||||
for (final following in nym.value!.following) {
|
||||
codesToCheck.add(following.code);
|
||||
}
|
||||
}
|
||||
|
||||
final currentHeight = await chainHeight;
|
||||
const storedHeight = 1; //await storedChainHeight;
|
||||
|
@ -906,6 +993,9 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
|
||||
await _checkCurrentReceivingAddressesForTransactions();
|
||||
|
||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.4, walletId));
|
||||
await checkAllCurrentReceivingPaynymAddressesForTransactions();
|
||||
|
||||
final fetchFuture = _refreshTransactions();
|
||||
final utxosRefreshFuture = _updateUTXOs();
|
||||
GlobalEventBus.instance
|
||||
|
@ -924,6 +1014,7 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.fire(RefreshPercentChangedEvent(0.80, walletId));
|
||||
|
||||
await fetchFuture;
|
||||
await checkForNotificationTransactionsTo(codesToCheck);
|
||||
await getAllTxsToWatch();
|
||||
GlobalEventBus.instance
|
||||
.fire(RefreshPercentChangedEvent(0.90, walletId));
|
||||
|
@ -1176,46 +1267,31 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
// transactions locally in a good way
|
||||
@override
|
||||
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
|
||||
// final priceData =
|
||||
// await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
|
||||
// Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
||||
// final locale =
|
||||
// Platform.isWindows ? "en_US" : await Devicelocale.currentLocale;
|
||||
// final String worthNow = Format.localizedStringAsFixed(
|
||||
// value:
|
||||
// ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) /
|
||||
// Decimal.fromInt(Constants.satsPerCoin(coin)))
|
||||
// .toDecimal(scaleOnInfinitePrecision: 2),
|
||||
// decimalPlaces: 2,
|
||||
// locale: locale!);
|
||||
//
|
||||
// final tx = models.Transaction(
|
||||
// txid: txData["txid"] as String,
|
||||
// confirmedStatus: false,
|
||||
// timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
// txType: "Sent",
|
||||
// amount: txData["recipientAmt"] as int,
|
||||
// worthNow: worthNow,
|
||||
// worthAtBlockTimestamp: worthNow,
|
||||
// fees: txData["fee"] as int,
|
||||
// inputSize: 0,
|
||||
// outputSize: 0,
|
||||
// inputs: [],
|
||||
// outputs: [],
|
||||
// address: txData["address"] as String,
|
||||
// height: -1,
|
||||
// confirmations: 0,
|
||||
// );
|
||||
//
|
||||
// if (cachedTxData == null) {
|
||||
// final data = await _fetchTransactionData();
|
||||
// _transactionData = Future(() => data);
|
||||
// }
|
||||
//
|
||||
// final transactions = cachedTxData!.getAllTransactions();
|
||||
// transactions[tx.txid] = tx;
|
||||
// cachedTxData = models.TransactionData.fromMap(transactions);
|
||||
// _transactionData = Future(() => cachedTxData!);
|
||||
final transaction = isar_models.Transaction(
|
||||
walletId: walletId,
|
||||
txid: txData["txid"] as String,
|
||||
timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
type: isar_models.TransactionType.outgoing,
|
||||
subType: isar_models.TransactionSubType.none,
|
||||
amount: txData["recipientAmt"] as int,
|
||||
fee: txData["fee"] as int,
|
||||
height: null,
|
||||
isCancelled: false,
|
||||
isLelantus: false,
|
||||
otherData: null,
|
||||
slateId: null,
|
||||
);
|
||||
|
||||
final address = txData["address"] is String
|
||||
? await db.getAddress(walletId, txData["address"] as String)
|
||||
: null;
|
||||
|
||||
await db.addNewTransactionData(
|
||||
[
|
||||
Tuple4(transaction, [], [], address),
|
||||
],
|
||||
walletId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1264,6 +1340,29 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
_secureStore = secureStore;
|
||||
initCache(walletId, coin);
|
||||
initWalletDB(mockableOverride: mockableOverride);
|
||||
initPaynymWalletInterface(
|
||||
walletId: walletId,
|
||||
walletName: walletName,
|
||||
network: _network,
|
||||
coin: coin,
|
||||
db: db,
|
||||
electrumXClient: electrumXClient,
|
||||
secureStorage: secureStore,
|
||||
getMnemonic: () => mnemonic,
|
||||
getChainHeight: () => chainHeight,
|
||||
getCurrentChangeAddress: () => currentChangeAddressP2PKH,
|
||||
estimateTxFee: estimateTxFee,
|
||||
prepareSend: prepareSend,
|
||||
getTxCount: getTxCount,
|
||||
fetchBuildTxData: fetchBuildTxData,
|
||||
refresh: refresh,
|
||||
checkChangeAddressForTransactions:
|
||||
_checkP2PKHChangeAddressForTransactions,
|
||||
addDerivation: addDerivation,
|
||||
addDerivations: addDerivations,
|
||||
dustLimitP2PKH: DUST_LIMIT_P2PKH,
|
||||
minConfirms: MINIMUM_CONFIRMATIONS,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1324,62 +1423,11 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.not()
|
||||
.typeEqualTo(isar_models.AddressType.unknown)
|
||||
.and()
|
||||
.not()
|
||||
.typeEqualTo(isar_models.AddressType.nonWallet)
|
||||
.and()
|
||||
.group((q) => q
|
||||
.subTypeEqualTo(isar_models.AddressSubType.receiving)
|
||||
.or()
|
||||
.subTypeEqualTo(isar_models.AddressSubType.change))
|
||||
.not()
|
||||
.subTypeEqualTo(isar_models.AddressSubType.nonWallet)
|
||||
.findAll();
|
||||
// final List<String> allAddresses = [];
|
||||
// final receivingAddresses = DB.instance.get<dynamic>(
|
||||
// boxName: walletId, key: 'receivingAddressesP2WPKH') as List<dynamic>;
|
||||
// final changeAddresses = DB.instance.get<dynamic>(
|
||||
// boxName: walletId, key: 'changeAddressesP2WPKH') as List<dynamic>;
|
||||
// final receivingAddressesP2PKH = DB.instance.get<dynamic>(
|
||||
// boxName: walletId, key: 'receivingAddressesP2PKH') as List<dynamic>;
|
||||
// final changeAddressesP2PKH =
|
||||
// DB.instance.get<dynamic>(boxName: walletId, key: 'changeAddressesP2PKH')
|
||||
// as List<dynamic>;
|
||||
// final receivingAddressesP2SH = DB.instance.get<dynamic>(
|
||||
// boxName: walletId, key: 'receivingAddressesP2SH') as List<dynamic>;
|
||||
// final changeAddressesP2SH =
|
||||
// DB.instance.get<dynamic>(boxName: walletId, key: 'changeAddressesP2SH')
|
||||
// as List<dynamic>;
|
||||
//
|
||||
// for (var i = 0; i < receivingAddresses.length; i++) {
|
||||
// if (!allAddresses.contains(receivingAddresses[i])) {
|
||||
// allAddresses.add(receivingAddresses[i] as String);
|
||||
// }
|
||||
// }
|
||||
// for (var i = 0; i < changeAddresses.length; i++) {
|
||||
// if (!allAddresses.contains(changeAddresses[i])) {
|
||||
// allAddresses.add(changeAddresses[i] as String);
|
||||
// }
|
||||
// }
|
||||
// for (var i = 0; i < receivingAddressesP2PKH.length; i++) {
|
||||
// if (!allAddresses.contains(receivingAddressesP2PKH[i])) {
|
||||
// allAddresses.add(receivingAddressesP2PKH[i] as String);
|
||||
// }
|
||||
// }
|
||||
// for (var i = 0; i < changeAddressesP2PKH.length; i++) {
|
||||
// if (!allAddresses.contains(changeAddressesP2PKH[i])) {
|
||||
// allAddresses.add(changeAddressesP2PKH[i] as String);
|
||||
// }
|
||||
// }
|
||||
// for (var i = 0; i < receivingAddressesP2SH.length; i++) {
|
||||
// if (!allAddresses.contains(receivingAddressesP2SH[i])) {
|
||||
// allAddresses.add(receivingAddressesP2SH[i] as String);
|
||||
// }
|
||||
// }
|
||||
// for (var i = 0; i < changeAddressesP2SH.length; i++) {
|
||||
// if (!allAddresses.contains(changeAddressesP2SH[i])) {
|
||||
// allAddresses.add(changeAddressesP2SH[i] as String);
|
||||
// }
|
||||
// }
|
||||
return allAddresses;
|
||||
}
|
||||
|
||||
|
@ -1509,6 +1557,8 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
address = P2WPKH(network: _network, data: data).data.address!;
|
||||
addrType = isar_models.AddressType.p2wpkh;
|
||||
break;
|
||||
default:
|
||||
throw Exception("DerivePathType $derivePathType not supported");
|
||||
}
|
||||
|
||||
// add generated address & info to derivations
|
||||
|
@ -1555,6 +1605,8 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip84:
|
||||
type = isar_models.AddressType.p2wpkh;
|
||||
break;
|
||||
default:
|
||||
throw Exception("DerivePathType unsupported");
|
||||
}
|
||||
address = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1582,6 +1634,8 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip84:
|
||||
key = "${walletId}_${chainId}DerivationsP2WPKH";
|
||||
break;
|
||||
default:
|
||||
throw Exception("DerivePathType unsupported");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
@ -1754,15 +1808,34 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
coin: coin,
|
||||
);
|
||||
|
||||
// todo check here if we should mark as blocked
|
||||
// fetch stored tx to see if paynym notification tx and block utxo
|
||||
final storedTx = await db.getTransaction(
|
||||
walletId,
|
||||
fetchedUtxoList[i][j]["tx_hash"] as String,
|
||||
);
|
||||
|
||||
bool shouldBlock = false;
|
||||
String? blockReason;
|
||||
|
||||
if (storedTx?.subType ==
|
||||
isar_models.TransactionSubType.bip47Notification &&
|
||||
storedTx?.type == isar_models.TransactionType.incoming) {
|
||||
// probably safe to assume this is an incoming tx as it is a utxo
|
||||
// belonging to this wallet. The extra check may be redundant but
|
||||
// just in case...
|
||||
|
||||
shouldBlock = true;
|
||||
blockReason = "Incoming paynym notification transaction.";
|
||||
}
|
||||
|
||||
final utxo = isar_models.UTXO(
|
||||
walletId: walletId,
|
||||
txid: txn["txid"] as String,
|
||||
vout: fetchedUtxoList[i][j]["tx_pos"] as int,
|
||||
value: fetchedUtxoList[i][j]["value"] as int,
|
||||
name: "",
|
||||
isBlocked: false,
|
||||
blockedReason: null,
|
||||
isBlocked: shouldBlock,
|
||||
blockedReason: blockReason,
|
||||
isCoinbase: txn["is_coinbase"] as bool? ?? false,
|
||||
blockHash: txn["blockhash"] as String?,
|
||||
blockHeight: fetchedUtxoList[i][j]["height"] as int?,
|
||||
|
@ -1790,7 +1863,7 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.clear();
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
|
||||
|
@ -1868,7 +1941,7 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = await _generateAddressForChain(
|
||||
0, newReceivingIndex, DerivePathType.bip84);
|
||||
0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
final existing = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1887,7 +1960,7 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathType.bip84}): $e\n$s",
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -1907,7 +1980,7 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new change address
|
||||
final newChangeAddress = await _generateAddressForChain(
|
||||
1, newChangeIndex, DerivePathType.bip84);
|
||||
1, newChangeIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
final existing = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1926,12 +1999,56 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
} on SocketException catch (se, s) {
|
||||
Logging.instance.log(
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathType.bip84}): $se\n$s",
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s",
|
||||
level: LogLevel.Error);
|
||||
return;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathType.bip84}): $e\n$s",
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _checkP2PKHChangeAddressForTransactions() async {
|
||||
try {
|
||||
final currentChange = await _currentChangeAddressP2PKH;
|
||||
final int txCount = await getTxCount(address: currentChange.value);
|
||||
Logging.instance.log(
|
||||
'Number of txs for current change address $currentChange: $txCount',
|
||||
level: LogLevel.Info);
|
||||
|
||||
if (txCount >= 1 || currentChange.derivationIndex < 0) {
|
||||
// First increment the change index
|
||||
final newChangeIndex = currentChange.derivationIndex + 1;
|
||||
|
||||
// Use new index to derive a new change address
|
||||
final newChangeAddress = await _generateAddressForChain(
|
||||
1, newChangeIndex, DerivePathType.bip44);
|
||||
|
||||
final existing = await db
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.valueEqualTo(newChangeAddress.value)
|
||||
.findFirst();
|
||||
if (existing == null) {
|
||||
// Add that new change address
|
||||
await db.putAddress(newChangeAddress);
|
||||
} else {
|
||||
// we need to update the address
|
||||
await db.updateAddress(existing, newChangeAddress);
|
||||
}
|
||||
// keep checking until address with no tx history is set as current
|
||||
await _checkP2PKHChangeAddressForTransactions();
|
||||
}
|
||||
} on SocketException catch (se, s) {
|
||||
Logging.instance.log(
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathType.bip44}): $se\n$s",
|
||||
level: LogLevel.Error);
|
||||
return;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathType.bip44}): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -2166,6 +2283,8 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}",
|
||||
level: LogLevel.Info);
|
||||
Logging.instance.log("availableOutputs.length: ${availableOutputs.length}",
|
||||
level: LogLevel.Info);
|
||||
Logging.instance
|
||||
.log("spendableOutputs: $spendableOutputs", level: LogLevel.Info);
|
||||
Logging.instance.log("spendableSatoshiValue: $spendableSatoshiValue",
|
||||
|
@ -2258,24 +2377,38 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
return transactionObject;
|
||||
}
|
||||
|
||||
final int vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
final int vSizeForTwoOutPuts = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
await _getCurrentAddressForChain(1, DerivePathType.bip84),
|
||||
],
|
||||
satoshiAmounts: [
|
||||
satoshiAmountToSend,
|
||||
satoshisBeingUsed - satoshiAmountToSend - 1
|
||||
], // dust limit is the minimum amount a change output should be
|
||||
))["vSize"] as int;
|
||||
final int vSizeForOneOutput;
|
||||
try {
|
||||
vSizeForOneOutput = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [_recipientAddress],
|
||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||
))["vSize"] as int;
|
||||
} catch (e) {
|
||||
Logging.instance.log("vSizeForOneOutput: $e", level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
||||
final int vSizeForTwoOutPuts;
|
||||
try {
|
||||
vSizeForTwoOutPuts = (await buildTransaction(
|
||||
utxosToUse: utxoObjectsToUse,
|
||||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
await _getCurrentAddressForChain(
|
||||
1, DerivePathTypeExt.primaryFor(coin)),
|
||||
],
|
||||
satoshiAmounts: [
|
||||
satoshiAmountToSend,
|
||||
max(0, satoshisBeingUsed - satoshiAmountToSend - 1),
|
||||
],
|
||||
))["vSize"] as int;
|
||||
} catch (e) {
|
||||
Logging.instance.log("vSizeForTwoOutPuts: $e", level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
||||
// Assume 1 output, only for recipient and no change
|
||||
final feeForOneOutput = estimateTxFee(
|
||||
|
@ -2308,8 +2441,8 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
feeForTwoOutputs) {
|
||||
// generate new change address if current change address has been used
|
||||
await _checkChangeAddressForTransactions();
|
||||
final String newChangeAddress =
|
||||
await _getCurrentAddressForChain(1, DerivePathType.bip84);
|
||||
final String newChangeAddress = await _getCurrentAddressForChain(
|
||||
1, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
int feeBeingPaid =
|
||||
satoshisBeingUsed - satoshiAmountToSend - changeOutputSize;
|
||||
|
@ -2515,6 +2648,8 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip84:
|
||||
addressesP2WPKH.add(address);
|
||||
break;
|
||||
default:
|
||||
throw Exception("DerivePathType unsupported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2794,6 +2929,7 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
mnemonic: mnemonic!,
|
||||
maxUnusedAddressGap: maxUnusedAddressGap,
|
||||
maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
|
||||
isRescan: true,
|
||||
);
|
||||
|
||||
longMutex = false;
|
||||
|
@ -3035,7 +3171,7 @@ class BitcoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = await _generateAddressForChain(
|
||||
0, newReceivingIndex, DerivePathType.bip84);
|
||||
0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
// Add that new receiving address
|
||||
await db.putAddress(newReceivingAddress);
|
||||
|
|
|
@ -34,6 +34,7 @@ import 'package:stackwallet/utilities/assets.dart';
|
|||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
|
@ -50,8 +51,6 @@ const String GENESIS_HASH_MAINNET =
|
|||
const String GENESIS_HASH_TESTNET =
|
||||
"000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943";
|
||||
|
||||
enum DerivePathType { bip44, bip49 }
|
||||
|
||||
bip32.BIP32 getBip32Node(int chain, int index, String mnemonic,
|
||||
NetworkType network, DerivePathType derivePathType) {
|
||||
final root = getBip32Root(mnemonic, network);
|
||||
|
@ -78,7 +77,8 @@ bip32.BIP32 getBip32NodeFromRoot(
|
|||
String coinType;
|
||||
switch (root.network.wif) {
|
||||
case 0x80: // bch mainnet wif
|
||||
coinType = "145"; // bch mainnet
|
||||
coinType =
|
||||
derivePathType == DerivePathType.bch44 ? "145" : "0"; // bch mainnet
|
||||
break;
|
||||
case 0xef: // bch testnet wif
|
||||
coinType = "1"; // bch testnet
|
||||
|
@ -88,6 +88,7 @@ bip32.BIP32 getBip32NodeFromRoot(
|
|||
}
|
||||
switch (derivePathType) {
|
||||
case DerivePathType.bip44:
|
||||
case DerivePathType.bch44:
|
||||
return root.derivePath("m/44'/$coinType'/0'/$chain/$index");
|
||||
case DerivePathType.bip49:
|
||||
return root.derivePath("m/49'/$coinType'/0'/$chain/$index");
|
||||
|
@ -170,7 +171,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.subTypeEqualTo(isar_models.AddressSubType.receiving)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()) ??
|
||||
await _generateAddressForChain(0, 0, DerivePathType.bip44);
|
||||
await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
Future<String> get currentChangeAddress async =>
|
||||
(await _currentChangeAddress).value;
|
||||
|
@ -183,7 +184,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.subTypeEqualTo(isar_models.AddressSubType.change)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()) ??
|
||||
await _generateAddressForChain(1, 0, DerivePathType.bip44);
|
||||
await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
@override
|
||||
Future<void> exit() async {
|
||||
|
@ -218,6 +219,14 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
@ -322,6 +331,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
mnemonic: mnemonic.trim(),
|
||||
maxUnusedAddressGap: maxUnusedAddressGap,
|
||||
maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
|
||||
coin: coin,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
|
@ -380,6 +390,11 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
addrType = isar_models.AddressType.p2pkh;
|
||||
addressString = bitbox.Address.toCashAddress(addressString);
|
||||
break;
|
||||
case DerivePathType.bch44:
|
||||
addressString = P2PKH(data: data, network: _network).data.address!;
|
||||
addrType = isar_models.AddressType.unknown;
|
||||
addressString = bitbox.Address.toCashAddress(addressString);
|
||||
break;
|
||||
case DerivePathType.bip49:
|
||||
addressString = P2SH(
|
||||
data: PaymentData(
|
||||
|
@ -390,7 +405,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
addrType = isar_models.AddressType.p2sh;
|
||||
break;
|
||||
default:
|
||||
throw Exception("No Path type $type exists");
|
||||
throw Exception("DerivePathType $type not supported");
|
||||
}
|
||||
|
||||
final address = isar_models.Address(
|
||||
|
@ -480,24 +495,32 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
required String mnemonic,
|
||||
int maxUnusedAddressGap = 20,
|
||||
int maxNumberOfIndexesToCheck = 1000,
|
||||
bool isRescan = false,
|
||||
Coin? coin,
|
||||
}) async {
|
||||
longMutex = true;
|
||||
|
||||
Map<String, Map<String, String>> p2pkhReceiveDerivations = {};
|
||||
Map<String, Map<String, String>> bip44P2pkhReceiveDerivations = {};
|
||||
Map<String, Map<String, String>> bch44P2pkhReceiveDerivations = {};
|
||||
Map<String, Map<String, String>> p2shReceiveDerivations = {};
|
||||
Map<String, Map<String, String>> p2pkhChangeDerivations = {};
|
||||
Map<String, Map<String, String>> bip44P2pkhChangeDerivations = {};
|
||||
Map<String, Map<String, String>> bch44P2pkhChangeDerivations = {};
|
||||
Map<String, Map<String, String>> p2shChangeDerivations = {};
|
||||
|
||||
final root = await compute(getBip32RootWrapper, Tuple2(mnemonic, _network));
|
||||
|
||||
List<isar_models.Address> p2pkhReceiveAddressArray = [];
|
||||
List<isar_models.Address> bip44P2pkhReceiveAddressArray = [];
|
||||
List<isar_models.Address> bch44P2pkhReceiveAddressArray = [];
|
||||
List<isar_models.Address> p2shReceiveAddressArray = [];
|
||||
int p2pkhReceiveIndex = -1;
|
||||
int bch44P2pkhReceiveIndex = -1;
|
||||
int bip44P2pkhReceiveIndex = -1;
|
||||
int p2shReceiveIndex = -1;
|
||||
|
||||
List<isar_models.Address> p2pkhChangeAddressArray = [];
|
||||
List<isar_models.Address> bip44P2pkhChangeAddressArray = [];
|
||||
List<isar_models.Address> bch44P2pkhChangeAddressArray = [];
|
||||
List<isar_models.Address> p2shChangeAddressArray = [];
|
||||
int p2pkhChangeIndex = -1;
|
||||
int bch44P2pkhChangeIndex = -1;
|
||||
int bip44P2pkhChangeIndex = -1;
|
||||
int p2shChangeIndex = -1;
|
||||
|
||||
// The gap limit will be capped at [maxUnusedAddressGap]
|
||||
|
@ -508,10 +531,11 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
const txCountBatchSize = 12;
|
||||
|
||||
try {
|
||||
bool testnet = ((coin ?? null) == Coin.bitcoincashTestnet) ? true : false;
|
||||
// receiving addresses
|
||||
Logging.instance
|
||||
.log("checking receiving addresses...", level: LogLevel.Info);
|
||||
final resultReceive44 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||
final resultReceiveBip44 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0);
|
||||
|
||||
final resultReceive49 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||
|
@ -520,23 +544,23 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
Logging.instance
|
||||
.log("checking change addresses...", level: LogLevel.Info);
|
||||
// change addresses
|
||||
final resultChange44 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||
final bip44ResultChange = _checkGaps(maxNumberOfIndexesToCheck,
|
||||
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1);
|
||||
|
||||
final resultChange49 = _checkGaps(maxNumberOfIndexesToCheck,
|
||||
maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 1);
|
||||
|
||||
await Future.wait([
|
||||
resultReceive44,
|
||||
resultReceiveBip44,
|
||||
resultReceive49,
|
||||
resultChange44,
|
||||
bip44ResultChange,
|
||||
resultChange49,
|
||||
]);
|
||||
|
||||
p2pkhReceiveAddressArray =
|
||||
(await resultReceive44)['addressArray'] as List<isar_models.Address>;
|
||||
p2pkhReceiveIndex = (await resultReceive44)['index'] as int;
|
||||
p2pkhReceiveDerivations = (await resultReceive44)['derivations']
|
||||
bip44P2pkhReceiveAddressArray = (await resultReceiveBip44)['addressArray']
|
||||
as List<isar_models.Address>;
|
||||
bip44P2pkhReceiveIndex = (await resultReceiveBip44)['index'] as int;
|
||||
bip44P2pkhReceiveDerivations = (await resultReceiveBip44)['derivations']
|
||||
as Map<String, Map<String, String>>;
|
||||
|
||||
p2shReceiveAddressArray =
|
||||
|
@ -545,10 +569,10 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
p2shReceiveDerivations = (await resultReceive49)['derivations']
|
||||
as Map<String, Map<String, String>>;
|
||||
|
||||
p2pkhChangeAddressArray =
|
||||
(await resultChange44)['addressArray'] as List<isar_models.Address>;
|
||||
p2pkhChangeIndex = (await resultChange44)['index'] as int;
|
||||
p2pkhChangeDerivations = (await resultChange44)['derivations']
|
||||
bip44P2pkhChangeAddressArray = (await bip44ResultChange)['addressArray']
|
||||
as List<isar_models.Address>;
|
||||
bip44P2pkhChangeIndex = (await bip44ResultChange)['index'] as int;
|
||||
bip44P2pkhChangeDerivations = (await bip44ResultChange)['derivations']
|
||||
as Map<String, Map<String, String>>;
|
||||
|
||||
p2shChangeAddressArray =
|
||||
|
@ -558,11 +582,11 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
as Map<String, Map<String, String>>;
|
||||
|
||||
// save the derivations (if any)
|
||||
if (p2pkhReceiveDerivations.isNotEmpty) {
|
||||
if (bip44P2pkhReceiveDerivations.isNotEmpty) {
|
||||
await addDerivations(
|
||||
chain: 0,
|
||||
derivePathType: DerivePathType.bip44,
|
||||
derivationsToAdd: p2pkhReceiveDerivations);
|
||||
derivationsToAdd: bip44P2pkhReceiveDerivations);
|
||||
}
|
||||
if (p2shReceiveDerivations.isNotEmpty) {
|
||||
await addDerivations(
|
||||
|
@ -570,11 +594,11 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
derivePathType: DerivePathType.bip49,
|
||||
derivationsToAdd: p2shReceiveDerivations);
|
||||
}
|
||||
if (p2pkhChangeDerivations.isNotEmpty) {
|
||||
if (bip44P2pkhChangeDerivations.isNotEmpty) {
|
||||
await addDerivations(
|
||||
chain: 1,
|
||||
derivePathType: DerivePathType.bip44,
|
||||
derivationsToAdd: p2pkhChangeDerivations);
|
||||
derivationsToAdd: bip44P2pkhChangeDerivations);
|
||||
}
|
||||
if (p2shChangeDerivations.isNotEmpty) {
|
||||
await addDerivations(
|
||||
|
@ -585,10 +609,10 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// If restoring a wallet that never received any funds, then set receivingArray manually
|
||||
// If we didn't do this, it'd store an empty array
|
||||
if (p2pkhReceiveIndex == -1) {
|
||||
if (bip44P2pkhReceiveIndex == -1) {
|
||||
final address =
|
||||
await _generateAddressForChain(0, 0, DerivePathType.bip44);
|
||||
p2pkhReceiveAddressArray.add(address);
|
||||
bip44P2pkhReceiveAddressArray.add(address);
|
||||
}
|
||||
if (p2shReceiveIndex == -1) {
|
||||
final address =
|
||||
|
@ -598,10 +622,10 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// If restoring a wallet that never sent any funds with change, then set changeArray
|
||||
// manually. If we didn't do this, it'd store an empty array.
|
||||
if (p2pkhChangeIndex == -1) {
|
||||
if (bip44P2pkhChangeIndex == -1) {
|
||||
final address =
|
||||
await _generateAddressForChain(1, 0, DerivePathType.bip44);
|
||||
p2pkhChangeAddressArray.add(address);
|
||||
bip44P2pkhChangeAddressArray.add(address);
|
||||
}
|
||||
if (p2shChangeIndex == -1) {
|
||||
final address =
|
||||
|
@ -609,12 +633,82 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
p2shChangeAddressArray.add(address);
|
||||
}
|
||||
|
||||
await db.putAddresses([
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
final addressesToStore = [
|
||||
...bip44P2pkhReceiveAddressArray,
|
||||
...bip44P2pkhChangeAddressArray,
|
||||
...p2shReceiveAddressArray,
|
||||
...p2shChangeAddressArray,
|
||||
]);
|
||||
];
|
||||
|
||||
if (!testnet) {
|
||||
final resultReceiveBch44 = _checkGaps(
|
||||
maxNumberOfIndexesToCheck,
|
||||
maxUnusedAddressGap,
|
||||
txCountBatchSize,
|
||||
root,
|
||||
DerivePathType.bch44,
|
||||
0);
|
||||
final Future<Map<String, dynamic>> bch44ResultChange = _checkGaps(
|
||||
maxNumberOfIndexesToCheck,
|
||||
maxUnusedAddressGap,
|
||||
txCountBatchSize,
|
||||
root,
|
||||
DerivePathType.bch44,
|
||||
1);
|
||||
await Future.wait([
|
||||
resultReceiveBch44,
|
||||
bch44ResultChange,
|
||||
]);
|
||||
|
||||
bch44P2pkhReceiveAddressArray =
|
||||
(await resultReceiveBch44)['addressArray']
|
||||
as List<isar_models.Address>;
|
||||
bch44P2pkhReceiveIndex = (await resultReceiveBch44)['index'] as int;
|
||||
bch44P2pkhReceiveDerivations = (await resultReceiveBch44)['derivations']
|
||||
as Map<String, Map<String, String>>;
|
||||
|
||||
bch44P2pkhChangeAddressArray = (await bch44ResultChange)['addressArray']
|
||||
as List<isar_models.Address>;
|
||||
bch44P2pkhChangeIndex = (await bch44ResultChange)['index'] as int;
|
||||
bch44P2pkhChangeDerivations = (await bch44ResultChange)['derivations']
|
||||
as Map<String, Map<String, String>>;
|
||||
|
||||
if (bch44P2pkhReceiveDerivations.isNotEmpty) {
|
||||
await addDerivations(
|
||||
chain: 0,
|
||||
derivePathType: DerivePathType.bch44,
|
||||
derivationsToAdd: bch44P2pkhReceiveDerivations);
|
||||
}
|
||||
if (bch44P2pkhChangeDerivations.isNotEmpty) {
|
||||
await addDerivations(
|
||||
chain: 1,
|
||||
derivePathType: DerivePathType.bch44,
|
||||
derivationsToAdd: bch44P2pkhChangeDerivations);
|
||||
}
|
||||
|
||||
if (bch44P2pkhReceiveIndex == -1) {
|
||||
final address =
|
||||
await _generateAddressForChain(0, 0, DerivePathType.bch44);
|
||||
bch44P2pkhReceiveAddressArray.add(address);
|
||||
}
|
||||
|
||||
if (bch44P2pkhChangeIndex == -1) {
|
||||
final address =
|
||||
await _generateAddressForChain(1, 0, DerivePathType.bch44);
|
||||
bch44P2pkhChangeAddressArray.add(address);
|
||||
}
|
||||
|
||||
addressesToStore.addAll([
|
||||
...bch44P2pkhReceiveAddressArray,
|
||||
...bch44P2pkhChangeAddressArray,
|
||||
]);
|
||||
}
|
||||
|
||||
if (isRescan) {
|
||||
await db.updateOrPutAddresses(addressesToStore);
|
||||
} else {
|
||||
await db.putAddresses(addressesToStore);
|
||||
}
|
||||
|
||||
await _updateUTXOs();
|
||||
|
||||
|
@ -1106,46 +1200,31 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
// transactions locally in a good way
|
||||
@override
|
||||
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
|
||||
// final priceData =
|
||||
// await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
|
||||
// Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
||||
// final locale =
|
||||
// Platform.isWindows ? "en_US" : await Devicelocale.currentLocale;
|
||||
// final String worthNow = Format.localizedStringAsFixed(
|
||||
// value:
|
||||
// ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) /
|
||||
// Decimal.fromInt(Constants.satsPerCoin(coin)))
|
||||
// .toDecimal(scaleOnInfinitePrecision: 2),
|
||||
// decimalPlaces: 2,
|
||||
// locale: locale!);
|
||||
//
|
||||
// final tx = models.Transaction(
|
||||
// txid: txData["txid"] as String,
|
||||
// confirmedStatus: false,
|
||||
// timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
// txType: "Sent",
|
||||
// amount: txData["recipientAmt"] as int,
|
||||
// worthNow: worthNow,
|
||||
// worthAtBlockTimestamp: worthNow,
|
||||
// fees: txData["fee"] as int,
|
||||
// inputSize: 0,
|
||||
// outputSize: 0,
|
||||
// inputs: [],
|
||||
// outputs: [],
|
||||
// address: txData["address"] as String,
|
||||
// height: -1,
|
||||
// confirmations: 0,
|
||||
// );
|
||||
//
|
||||
// if (cachedTxData == null) {
|
||||
// final data = await _fetchTransactionData();
|
||||
// _transactionData = Future(() => data);
|
||||
// }
|
||||
//
|
||||
// final transactions = cachedTxData!.getAllTransactions();
|
||||
// transactions[tx.txid] = tx;
|
||||
// cachedTxData = models.TransactionData.fromMap(transactions);
|
||||
// _transactionData = Future(() => cachedTxData!);
|
||||
final transaction = isar_models.Transaction(
|
||||
walletId: walletId,
|
||||
txid: txData["txid"] as String,
|
||||
timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
type: isar_models.TransactionType.outgoing,
|
||||
subType: isar_models.TransactionSubType.none,
|
||||
amount: txData["recipientAmt"] as int,
|
||||
fee: txData["fee"] as int,
|
||||
height: null,
|
||||
isCancelled: false,
|
||||
isLelantus: false,
|
||||
otherData: null,
|
||||
slateId: null,
|
||||
);
|
||||
|
||||
final address = txData["address"] is String
|
||||
? await db.getAddress(walletId, txData["address"] as String)
|
||||
: null;
|
||||
|
||||
await db.addNewTransactionData(
|
||||
[
|
||||
Tuple4(transaction, [], [], address),
|
||||
],
|
||||
walletId,
|
||||
);
|
||||
}
|
||||
|
||||
bool validateCashAddr(String cashAddr) {
|
||||
|
@ -1403,6 +1482,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
),
|
||||
);
|
||||
final data = PaymentData(pubkey: node.publicKey);
|
||||
|
||||
String address;
|
||||
isar_models.AddressType addrType;
|
||||
|
||||
|
@ -1412,6 +1492,11 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
addrType = isar_models.AddressType.p2pkh;
|
||||
address = bitbox.Address.toCashAddress(address);
|
||||
break;
|
||||
case DerivePathType.bch44:
|
||||
address = P2PKH(data: data, network: _network).data.address!;
|
||||
addrType = isar_models.AddressType.unknown;
|
||||
address = bitbox.Address.toCashAddress(address);
|
||||
break;
|
||||
case DerivePathType.bip49:
|
||||
address = P2SH(
|
||||
data: PaymentData(
|
||||
|
@ -1421,9 +1506,10 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.address!;
|
||||
addrType = isar_models.AddressType.p2sh;
|
||||
break;
|
||||
// default:
|
||||
// // should never hit this due to all enum cases handled
|
||||
// return null;
|
||||
case DerivePathType.bip84:
|
||||
throw UnsupportedError("bip84 not supported by BCH");
|
||||
default:
|
||||
throw Exception("DerivePathType $derivePathType not supported");
|
||||
}
|
||||
|
||||
// add generated address & info to derivations
|
||||
|
@ -1463,9 +1549,16 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip44:
|
||||
type = isar_models.AddressType.p2pkh;
|
||||
break;
|
||||
case DerivePathType.bch44:
|
||||
type = isar_models.AddressType.unknown;
|
||||
break;
|
||||
case DerivePathType.bip49:
|
||||
type = isar_models.AddressType.p2sh;
|
||||
break;
|
||||
case DerivePathType.bip84:
|
||||
throw UnsupportedError("bip84 not supported by BCH");
|
||||
default:
|
||||
throw Exception("DerivePathType $derivePathType not supported");
|
||||
}
|
||||
|
||||
final address = await db
|
||||
|
@ -1488,9 +1581,14 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip44:
|
||||
key = "${walletId}_${chainId}DerivationsP2PKH";
|
||||
break;
|
||||
case DerivePathType.bch44:
|
||||
key = "${walletId}_${chainId}DerivationsBch44P2PKH";
|
||||
break;
|
||||
case DerivePathType.bip49:
|
||||
key = "${walletId}_${chainId}DerivationsP2SH";
|
||||
break;
|
||||
case DerivePathType.bip84:
|
||||
throw UnsupportedError("bip84 not supported by BCH");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
@ -1663,7 +1761,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.clear();
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
|
||||
|
@ -1752,7 +1850,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = await _generateAddressForChain(
|
||||
0, newReceivingIndex, DerivePathType.bip44);
|
||||
0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
final existing = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1771,12 +1869,12 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
} on SocketException catch (se, s) {
|
||||
Logging.instance.log(
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathType.bip44}): $se\n$s",
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s",
|
||||
level: LogLevel.Error);
|
||||
return;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathType.bip44}): $e\n$s",
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -1796,7 +1894,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new change address
|
||||
final newChangeAddress = await _generateAddressForChain(
|
||||
1, newChangeIndex, DerivePathType.bip44);
|
||||
1, newChangeIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
final existing = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1815,12 +1913,12 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
} on SocketException catch (se, s) {
|
||||
Logging.instance.log(
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathType.bip44}): $se\n$s",
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s",
|
||||
level: LogLevel.Error);
|
||||
return;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathType.bip44}): $e\n$s",
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -1951,7 +2049,8 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.where((e) => e.subType == isar_models.AddressSubType.receiving)
|
||||
.map((e) {
|
||||
if (bitbox.Address.detectFormat(e.value) == bitbox.Address.formatLegacy &&
|
||||
addressType(address: e.value) == DerivePathType.bip44) {
|
||||
(addressType(address: e.value) == DerivePathType.bip44 ||
|
||||
addressType(address: e.value) == DerivePathType.bch44)) {
|
||||
return bitbox.Address.toCashAddress(e.value);
|
||||
} else {
|
||||
return e.value;
|
||||
|
@ -1962,7 +2061,8 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.where((e) => e.subType == isar_models.AddressSubType.change)
|
||||
.map((e) {
|
||||
if (bitbox.Address.detectFormat(e.value) == bitbox.Address.formatLegacy &&
|
||||
addressType(address: e.value) == DerivePathType.bip44) {
|
||||
(addressType(address: e.value) == DerivePathType.bip44 ||
|
||||
addressType(address: e.value) == DerivePathType.bch44)) {
|
||||
return bitbox.Address.toCashAddress(e.value);
|
||||
} else {
|
||||
return e.value;
|
||||
|
@ -2346,7 +2446,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
await _getCurrentAddressForChain(1, DerivePathType.bip44),
|
||||
await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)),
|
||||
],
|
||||
satoshiAmounts: [
|
||||
satoshiAmountToSend,
|
||||
|
@ -2399,8 +2499,8 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
feeForTwoOutputs) {
|
||||
// generate new change address if current change address has been used
|
||||
await _checkChangeAddressForTransactions();
|
||||
final String newChangeAddress =
|
||||
await _getCurrentAddressForChain(1, DerivePathType.bip44);
|
||||
final String newChangeAddress = await _getCurrentAddressForChain(
|
||||
1, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
int feeBeingPaid =
|
||||
satoshisBeingUsed - satoshiAmountToSend - changeOutputSize;
|
||||
|
@ -2591,13 +2691,12 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final n = output["n"];
|
||||
if (n != null && n == utxosToUse[i].vout) {
|
||||
String address = output["scriptPubKey"]["addresses"][0] as String;
|
||||
if (bitbox.Address.detectFormat(address) ==
|
||||
if (bitbox.Address.detectFormat(address) !=
|
||||
bitbox.Address.formatCashAddr) {
|
||||
if (validateCashAddr(address)) {
|
||||
address = bitbox.Address.toLegacyAddress(address);
|
||||
} else {
|
||||
throw Exception(
|
||||
"Unsupported address found during fetchBuildTxData(): $address");
|
||||
try {
|
||||
address = bitbox.Address.toCashAddress(address);
|
||||
} catch (_) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
if (!addressTxid.containsKey(address)) {
|
||||
|
@ -2606,11 +2705,14 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
(addressTxid[address] as List).add(txid);
|
||||
switch (addressType(address: address)) {
|
||||
case DerivePathType.bip44:
|
||||
case DerivePathType.bch44:
|
||||
addressesP2PKH.add(address);
|
||||
break;
|
||||
case DerivePathType.bip49:
|
||||
addressesP2SH.add(address);
|
||||
break;
|
||||
case DerivePathType.bip84:
|
||||
throw UnsupportedError("bip84 not supported by BCH");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2619,19 +2721,28 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
// p2pkh / bip44
|
||||
final p2pkhLength = addressesP2PKH.length;
|
||||
if (p2pkhLength > 0) {
|
||||
final receiveDerivations = await _fetchDerivations(
|
||||
final receiveDerivationsBip44 = await _fetchDerivations(
|
||||
chain: 0,
|
||||
derivePathType: DerivePathType.bip44,
|
||||
);
|
||||
final changeDerivations = await _fetchDerivations(
|
||||
final changeDerivationsBip44 = await _fetchDerivations(
|
||||
chain: 1,
|
||||
derivePathType: DerivePathType.bip44,
|
||||
);
|
||||
final receiveDerivationsBch44 = await _fetchDerivations(
|
||||
chain: 0,
|
||||
derivePathType: DerivePathType.bch44,
|
||||
);
|
||||
final changeDerivationsBch44 = await _fetchDerivations(
|
||||
chain: 1,
|
||||
derivePathType: DerivePathType.bch44,
|
||||
);
|
||||
for (int i = 0; i < p2pkhLength; i++) {
|
||||
String address = addressesP2PKH[i];
|
||||
|
||||
// receives
|
||||
final receiveDerivation = receiveDerivations[address];
|
||||
final receiveDerivation = receiveDerivationsBip44[address] ??
|
||||
receiveDerivationsBch44[address];
|
||||
// if a match exists it will not be null
|
||||
if (receiveDerivation != null) {
|
||||
final data = P2PKH(
|
||||
|
@ -2652,7 +2763,8 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
} else {
|
||||
// if its not a receive, check change
|
||||
final changeDerivation = changeDerivations[address];
|
||||
final changeDerivation = changeDerivationsBip44[address] ??
|
||||
changeDerivationsBch44[address];
|
||||
// if a match exists it will not be null
|
||||
if (changeDerivation != null) {
|
||||
final data = P2PKH(
|
||||
|
@ -2863,6 +2975,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
mnemonic: mnemonic!,
|
||||
maxUnusedAddressGap: maxUnusedAddressGap,
|
||||
maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
|
||||
isRescan: true,
|
||||
);
|
||||
|
||||
longMutex = false;
|
||||
|
@ -2997,7 +3110,7 @@ class BitcoinCashWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = await _generateAddressForChain(
|
||||
0, newReceivingIndex, DerivePathType.bip44);
|
||||
0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
// Add that new receiving address
|
||||
await db.putAddress(newReceivingAddress);
|
||||
|
|
|
@ -14,16 +14,17 @@ import 'package:isar/isar.dart';
|
|||
import 'package:stackwallet/db/main_db.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||
import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart';
|
||||
import 'package:stackwallet/models/balance.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||
import 'package:stackwallet/services/coins/coin_service.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/services/mixins/electrum_x_parsing.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
||||
import 'package:stackwallet/services/node_service.dart';
|
||||
|
@ -34,6 +35,7 @@ import 'package:stackwallet/utilities/assets.dart';
|
|||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
|
@ -50,8 +52,6 @@ const String GENESIS_HASH_MAINNET =
|
|||
const String GENESIS_HASH_TESTNET =
|
||||
"bb0a78264637406b6360aad926284d544d7049f45189db5664f3c4d07350559e";
|
||||
|
||||
enum DerivePathType { bip44 }
|
||||
|
||||
bip32.BIP32 getBip32Node(int chain, int index, String mnemonic,
|
||||
NetworkType network, DerivePathType derivePathType) {
|
||||
final root = getBip32Root(mnemonic, network);
|
||||
|
@ -90,7 +90,8 @@ bip32.BIP32 getBip32NodeFromRoot(
|
|||
case DerivePathType.bip44:
|
||||
return root.derivePath("m/44'/$coinType'/0'/$chain/$index");
|
||||
default:
|
||||
throw Exception("DerivePathType must not be null.");
|
||||
throw Exception(
|
||||
"DerivePathType null or unsupported (${DerivePathType.bip44})");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,7 +126,8 @@ bip32.BIP32 getBip32RootWrapper(Tuple2<String, NetworkType> args) {
|
|||
return getBip32Root(args.item1, args.item2);
|
||||
}
|
||||
|
||||
class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
||||
class DogecoinWallet extends CoinServiceAPI
|
||||
with WalletCache, WalletDB, ElectrumXParsing {
|
||||
static const integrationTestFlag =
|
||||
bool.fromEnvironment("IS_INTEGRATION_TEST");
|
||||
final _prefs = Prefs.instance;
|
||||
|
@ -150,8 +152,16 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
Future<List<isar_models.UTXO>> get utxos => db.getUTXOs(walletId).findAll();
|
||||
|
||||
@override
|
||||
Future<List<isar_models.Transaction>> get transactions =>
|
||||
db.getTransactions(walletId).sortByTimestampDesc().findAll();
|
||||
Future<List<isar_models.Transaction>> get transactions => db
|
||||
.getTransactions(walletId)
|
||||
.filter()
|
||||
.not()
|
||||
.group((q) => q
|
||||
.subTypeEqualTo(isar_models.TransactionSubType.bip47Notification)
|
||||
.and()
|
||||
.typeEqualTo(isar_models.TransactionType.incoming))
|
||||
.sortByTimestampDesc()
|
||||
.findAll();
|
||||
|
||||
@override
|
||||
Coin get coin => _coin;
|
||||
|
@ -168,7 +178,7 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.subTypeEqualTo(isar_models.AddressSubType.receiving)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()) ??
|
||||
await _generateAddressForChain(0, 0, DerivePathType.bip44);
|
||||
await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
// @override
|
||||
Future<String> get currentChangeAddress async =>
|
||||
|
@ -182,7 +192,7 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.subTypeEqualTo(isar_models.AddressSubType.change)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()) ??
|
||||
await _generateAddressForChain(1, 0, DerivePathType.bip44);
|
||||
await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
@override
|
||||
Future<void> exit() async {
|
||||
|
@ -217,6 +227,14 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
@ -371,7 +389,7 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
);
|
||||
break;
|
||||
default:
|
||||
throw Exception("No Path type $type exists");
|
||||
throw Exception("DerivePathType $type not supported");
|
||||
}
|
||||
receivingNodes.addAll({
|
||||
"${_id}_$j": {
|
||||
|
@ -447,6 +465,7 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
required String mnemonic,
|
||||
int maxUnusedAddressGap = 20,
|
||||
int maxNumberOfIndexesToCheck = 1000,
|
||||
bool isRescan = false,
|
||||
}) async {
|
||||
longMutex = true;
|
||||
|
||||
|
@ -523,11 +542,30 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
await _generateAddressForChain(1, 0, DerivePathType.bip44);
|
||||
p2pkhChangeAddressArray.add(address);
|
||||
}
|
||||
if (isRescan) {
|
||||
await db.updateOrPutAddresses([
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
]);
|
||||
} else {
|
||||
await db.putAddresses([
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
]);
|
||||
}
|
||||
|
||||
await db.putAddresses([
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
]);
|
||||
// paynym stuff
|
||||
// // generate to ensure notification address is in db before refreshing transactions
|
||||
// await getMyNotificationAddress(DerivePathType.bip44);
|
||||
//
|
||||
// // refresh transactions to pick up any received notification transactions
|
||||
// await _refreshTransactions();
|
||||
//
|
||||
// // restore paynym transactions
|
||||
// await restoreAllHistory(
|
||||
// maxUnusedAddressGap: maxUnusedAddressGap,
|
||||
// maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
|
||||
// );
|
||||
|
||||
await _updateUTXOs();
|
||||
|
||||
|
@ -596,6 +634,13 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
}
|
||||
return needsRefresh;
|
||||
} on NoSuchTransactionException catch (e) {
|
||||
// TODO: move direct transactions elsewhere
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.transactions.deleteByTxidWalletId(e.txid, walletId);
|
||||
});
|
||||
await txTracker.deleteTransaction(e.txid);
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception caught in refreshIfThereIsNewData: $e\n$s",
|
||||
|
@ -761,6 +806,9 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
|
||||
await _checkCurrentReceivingAddressesForTransactions();
|
||||
|
||||
// paynym stuff
|
||||
// await checkAllCurrentReceivingPaynymAddressesForTransactions();
|
||||
|
||||
final fetchFuture = _refreshTransactions();
|
||||
final utxosRefreshFuture = _updateUTXOs();
|
||||
GlobalEventBus.instance
|
||||
|
@ -1011,46 +1059,31 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
// transactions locally in a good way
|
||||
@override
|
||||
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
|
||||
// final priceData =
|
||||
// await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
|
||||
// Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
||||
// final locale =
|
||||
// Platform.isWindows ? "en_US" : await Devicelocale.currentLocale;
|
||||
// final String worthNow = Format.localizedStringAsFixed(
|
||||
// value:
|
||||
// ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) /
|
||||
// Decimal.fromInt(Constants.satsPerCoin(coin)))
|
||||
// .toDecimal(scaleOnInfinitePrecision: 2),
|
||||
// decimalPlaces: 2,
|
||||
// locale: locale!);
|
||||
//
|
||||
// final tx = models.Transaction(
|
||||
// txid: txData["txid"] as String,
|
||||
// confirmedStatus: false,
|
||||
// timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
// txType: "Sent",
|
||||
// amount: txData["recipientAmt"] as int,
|
||||
// worthNow: worthNow,
|
||||
// worthAtBlockTimestamp: worthNow,
|
||||
// fees: txData["fee"] as int,
|
||||
// inputSize: 0,
|
||||
// outputSize: 0,
|
||||
// inputs: [],
|
||||
// outputs: [],
|
||||
// address: txData["address"] as String,
|
||||
// height: -1,
|
||||
// confirmations: 0,
|
||||
// );
|
||||
//
|
||||
// if (cachedTxData == null) {
|
||||
// final data = await _fetchTransactionData();
|
||||
// _transactionData = Future(() => data);
|
||||
// }
|
||||
//
|
||||
// final transactions = cachedTxData!.getAllTransactions();
|
||||
// transactions[tx.txid] = tx;
|
||||
// cachedTxData = models.TransactionData.fromMap(transactions);
|
||||
// _transactionData = Future(() => cachedTxData!);
|
||||
final transaction = isar_models.Transaction(
|
||||
walletId: walletId,
|
||||
txid: txData["txid"] as String,
|
||||
timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
type: isar_models.TransactionType.outgoing,
|
||||
subType: isar_models.TransactionSubType.none,
|
||||
amount: txData["recipientAmt"] as int,
|
||||
fee: txData["fee"] as int,
|
||||
height: null,
|
||||
isCancelled: false,
|
||||
isLelantus: false,
|
||||
otherData: null,
|
||||
slateId: null,
|
||||
);
|
||||
|
||||
final address = txData["address"] is String
|
||||
? await db.getAddress(walletId, txData["address"] as String)
|
||||
: null;
|
||||
|
||||
await db.addNewTransactionData(
|
||||
[
|
||||
Tuple4(transaction, [], [], address),
|
||||
],
|
||||
walletId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1099,6 +1132,27 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
_secureStore = secureStore;
|
||||
initCache(walletId, coin);
|
||||
initWalletDB(mockableOverride: mockableOverride);
|
||||
|
||||
// paynym stuff
|
||||
// initPaynymWalletInterface(
|
||||
// walletId: walletId,
|
||||
// walletName: walletName,
|
||||
// network: network,
|
||||
// coin: coin,
|
||||
// db: db,
|
||||
// electrumXClient: electrumXClient,
|
||||
// getMnemonic: () => mnemonic,
|
||||
// getChainHeight: () => chainHeight,
|
||||
// getCurrentChangeAddress: () => currentChangeAddress,
|
||||
// estimateTxFee: estimateTxFee,
|
||||
// prepareSend: prepareSend,
|
||||
// getTxCount: getTxCount,
|
||||
// fetchBuildTxData: fetchBuildTxData,
|
||||
// refresh: refresh,
|
||||
// checkChangeAddressForTransactions: checkChangeAddressForTransactions,
|
||||
// addDerivation: addDerivation,
|
||||
// addDerivations: addDerivations,
|
||||
// );
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1161,10 +1215,8 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.not()
|
||||
.typeEqualTo(isar_models.AddressType.nonWallet)
|
||||
.and()
|
||||
.group((q) => q
|
||||
.subTypeEqualTo(isar_models.AddressSubType.receiving)
|
||||
.or()
|
||||
.subTypeEqualTo(isar_models.AddressSubType.change))
|
||||
.not()
|
||||
.subTypeEqualTo(isar_models.AddressSubType.nonWallet)
|
||||
.findAll();
|
||||
return allAddresses;
|
||||
}
|
||||
|
@ -1274,9 +1326,8 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip44:
|
||||
address = P2PKH(data: data, network: network).data.address!;
|
||||
break;
|
||||
// default:
|
||||
// // should never hit this due to all enum cases handled
|
||||
// return null;
|
||||
default:
|
||||
throw Exception("Unsupported DerivePathType");
|
||||
}
|
||||
|
||||
// add generated address & info to derivations
|
||||
|
@ -1321,6 +1372,8 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.sortByDerivationIndexDesc()
|
||||
.findFirst();
|
||||
break;
|
||||
default:
|
||||
throw Exception("Unsupported DerivePathType");
|
||||
}
|
||||
return address!.value;
|
||||
}
|
||||
|
@ -1333,6 +1386,8 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip44:
|
||||
key = "${walletId}_${chainId}DerivationsP2PKH";
|
||||
break;
|
||||
default:
|
||||
throw Exception("Unsupported DerivePathType");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
@ -1503,15 +1558,34 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
coin: coin,
|
||||
);
|
||||
|
||||
// todo check here if we should mark as blocked
|
||||
// fetch stored tx to see if paynym notification tx and block utxo
|
||||
final storedTx = await db.getTransaction(
|
||||
walletId,
|
||||
fetchedUtxoList[i][j]["tx_hash"] as String,
|
||||
);
|
||||
|
||||
bool shouldBlock = false;
|
||||
String? blockReason;
|
||||
|
||||
if (storedTx?.subType ==
|
||||
isar_models.TransactionSubType.bip47Notification &&
|
||||
storedTx?.type == isar_models.TransactionType.incoming) {
|
||||
// probably safe to assume this is an incoming tx as it is a utxo
|
||||
// belonging to this wallet. The extra check may be redundant but
|
||||
// just in case...
|
||||
|
||||
shouldBlock = true;
|
||||
blockReason = "Incoming paynym notification transaction.";
|
||||
}
|
||||
|
||||
final utxo = isar_models.UTXO(
|
||||
walletId: walletId,
|
||||
txid: txn["txid"] as String,
|
||||
vout: fetchedUtxoList[i][j]["tx_pos"] as int,
|
||||
value: fetchedUtxoList[i][j]["value"] as int,
|
||||
name: "",
|
||||
isBlocked: false,
|
||||
blockedReason: null,
|
||||
isBlocked: shouldBlock,
|
||||
blockedReason: blockReason,
|
||||
isCoinbase: txn["is_coinbase"] as bool? ?? false,
|
||||
blockHash: txn["blockhash"] as String?,
|
||||
blockHeight: fetchedUtxoList[i][j]["height"] as int?,
|
||||
|
@ -1539,7 +1613,7 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.clear();
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
|
||||
|
@ -1656,7 +1730,7 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = await _generateAddressForChain(
|
||||
0, newReceivingIndex, DerivePathType.bip44);
|
||||
0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
final existing = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1675,12 +1749,12 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
} on SocketException catch (se, s) {
|
||||
Logging.instance.log(
|
||||
"SocketException caught in _checkReceivingAddressForTransactions($DerivePathType.bip44): $se\n$s",
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s",
|
||||
level: LogLevel.Error);
|
||||
return;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions($DerivePathType.bip44): $e\n$s",
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -1700,7 +1774,7 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new change address
|
||||
final newChangeAddress = await _generateAddressForChain(
|
||||
1, newChangeIndex, DerivePathType.bip44);
|
||||
1, newChangeIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
final existing = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1719,7 +1793,7 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkChangeAddressForTransactions(${DerivePathType.bip44}): $e\n$s",
|
||||
"Exception rethrown from _checkChangeAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -2088,7 +2162,7 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
await _getCurrentAddressForChain(1, DerivePathType.bip44),
|
||||
await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)),
|
||||
],
|
||||
satoshiAmounts: [
|
||||
satoshiAmountToSend,
|
||||
|
@ -2141,8 +2215,8 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
feeForTwoOutputs) {
|
||||
// generate new change address if current change address has been used
|
||||
await checkChangeAddressForTransactions();
|
||||
final String newChangeAddress =
|
||||
await _getCurrentAddressForChain(1, DerivePathType.bip44);
|
||||
final String newChangeAddress = await _getCurrentAddressForChain(
|
||||
1, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
int feeBeingPaid =
|
||||
satoshisBeingUsed - satoshiAmountToSend - changeOutputSize;
|
||||
|
@ -2340,6 +2414,8 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip44:
|
||||
addressesP2PKH.add(address);
|
||||
break;
|
||||
default:
|
||||
throw Exception("Unsupported DerivePathType");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2490,6 +2566,7 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
mnemonic: mnemonic!,
|
||||
maxUnusedAddressGap: maxUnusedAddressGap,
|
||||
maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
|
||||
isRescan: true,
|
||||
);
|
||||
|
||||
longMutex = false;
|
||||
|
@ -2757,7 +2834,7 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = await _generateAddressForChain(
|
||||
0, newReceivingIndex, DerivePathType.bip44);
|
||||
0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
// Add that new receiving address
|
||||
await db.putAddress(newReceivingAddress);
|
||||
|
@ -2775,7 +2852,7 @@ class DogecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
// Dogecoin Network
|
||||
final dogecoin = NetworkType(
|
||||
messagePrefix: '\x18Dogecoin Signed Message:\n',
|
||||
bech32: 'bc',
|
||||
// bech32: 'bc',
|
||||
bip32: Bip32Type(public: 0x02facafd, private: 0x02fac398),
|
||||
pubKeyHash: 0x1e,
|
||||
scriptHash: 0x16,
|
||||
|
@ -2783,7 +2860,7 @@ final dogecoin = NetworkType(
|
|||
|
||||
final dogecointestnet = NetworkType(
|
||||
messagePrefix: '\x18Dogecoin Signed Message:\n',
|
||||
bech32: 'tb',
|
||||
// bech32: 'tb',
|
||||
bip32: Bip32Type(public: 0x043587cf, private: 0x04358394),
|
||||
pubKeyHash: 0x71,
|
||||
scriptHash: 0xc4,
|
||||
|
|
|
@ -839,6 +839,10 @@ class EpicCashWallet extends CoinServiceAPI
|
|||
isar_models.Address? address = await db
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(isar_models.AddressSubType.receiving)
|
||||
.and()
|
||||
.typeEqualTo(isar_models.AddressType.mimbleWimble)
|
||||
.and()
|
||||
.derivationIndexEqualTo(index)
|
||||
.findFirst();
|
||||
|
||||
|
@ -877,8 +881,14 @@ class EpicCashWallet extends CoinServiceAPI
|
|||
(await _currentReceivingAddress)?.value ??
|
||||
(await _getReceivingAddressForIndex(0)).value;
|
||||
|
||||
Future<isar_models.Address?> get _currentReceivingAddress =>
|
||||
db.getAddresses(walletId).sortByDerivationIndexDesc().findFirst();
|
||||
Future<isar_models.Address?> get _currentReceivingAddress => db
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(isar_models.AddressSubType.receiving)
|
||||
.and()
|
||||
.typeEqualTo(isar_models.AddressType.mimbleWimble)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst();
|
||||
|
||||
@override
|
||||
Future<void> exit() async {
|
||||
|
@ -1416,6 +1426,14 @@ class EpicCashWallet extends CoinServiceAPI
|
|||
});
|
||||
|
||||
await updateCachedChainHeight(latestHeight!);
|
||||
if (latestHeight! > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return latestHeight!;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
|
|
@ -818,44 +818,31 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
|||
// transactions locally in a good way
|
||||
@override
|
||||
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
|
||||
// final currentPrice = await firoPrice;
|
||||
// final locale =
|
||||
// Platform.isWindows ? "en_US" : await Devicelocale.currentLocale;
|
||||
// final String worthNow = Format.localizedStringAsFixed(
|
||||
// value:
|
||||
// ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) /
|
||||
// Decimal.fromInt(Constants.satsPerCoin(coin)))
|
||||
// .toDecimal(scaleOnInfinitePrecision: 2),
|
||||
// decimalPlaces: 2,
|
||||
// locale: locale!);
|
||||
//
|
||||
// final tx = models.Transaction(
|
||||
// txid: txData["txid"] as String,
|
||||
// confirmedStatus: false,
|
||||
// timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
// txType: "Sent",
|
||||
// amount: txData["recipientAmt"] as int,
|
||||
// worthNow: worthNow,
|
||||
// worthAtBlockTimestamp: worthNow,
|
||||
// fees: txData["fee"] as int,
|
||||
// inputSize: 0,
|
||||
// outputSize: 0,
|
||||
// inputs: [],
|
||||
// outputs: [],
|
||||
// address: txData["address"] as String,
|
||||
// height: -1,
|
||||
// confirmations: 0,
|
||||
// );
|
||||
//
|
||||
// if (cachedTxData == null) {
|
||||
// final data = await _fetchTransactionData();
|
||||
// _transactionData = Future(() => data);
|
||||
// }
|
||||
//
|
||||
// final transactions = cachedTxData!.getAllTransactions();
|
||||
// transactions[tx.txid] = tx;
|
||||
// cachedTxData = models.TransactionData.fromMap(transactions);
|
||||
// _transactionData = Future(() => cachedTxData!);
|
||||
final transaction = isar_models.Transaction(
|
||||
walletId: walletId,
|
||||
txid: txData["txid"] as String,
|
||||
timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
type: isar_models.TransactionType.outgoing,
|
||||
subType: isar_models.TransactionSubType.none,
|
||||
amount: txData["recipientAmt"] as int,
|
||||
fee: txData["fee"] as int,
|
||||
height: null,
|
||||
isCancelled: false,
|
||||
isLelantus: false,
|
||||
otherData: null,
|
||||
slateId: null,
|
||||
);
|
||||
|
||||
final address = txData["address"] is String
|
||||
? await db.getAddress(walletId, txData["address"] as String)
|
||||
: null;
|
||||
|
||||
await db.addNewTransactionData(
|
||||
[
|
||||
Tuple4(transaction, [], [], address),
|
||||
],
|
||||
walletId,
|
||||
);
|
||||
}
|
||||
|
||||
/// Holds the max fee that can be sent
|
||||
|
@ -3665,7 +3652,7 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
|||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.clear();
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
|
||||
|
@ -4897,6 +4884,14 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
|
|
@ -17,13 +17,13 @@ import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
|||
import 'package:stackwallet/models/balance.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||
import 'package:stackwallet/services/coins/coin_service.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/services/mixins/electrum_x_parsing.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
||||
import 'package:stackwallet/services/node_service.dart';
|
||||
|
@ -33,6 +33,7 @@ import 'package:stackwallet/utilities/assets.dart';
|
|||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
|
@ -50,8 +51,6 @@ const String GENESIS_HASH_MAINNET =
|
|||
const String GENESIS_HASH_TESTNET =
|
||||
"4966625a4b2851d9fdee139e56211a0d88575f59ed816ff5e6a63deb4e3e29a0";
|
||||
|
||||
enum DerivePathType { bip44, bip49, bip84 }
|
||||
|
||||
bip32.BIP32 getBip32Node(
|
||||
int chain,
|
||||
int index,
|
||||
|
@ -103,7 +102,7 @@ bip32.BIP32 getBip32NodeFromRoot(
|
|||
case DerivePathType.bip84:
|
||||
return root.derivePath("m/84'/$coinType'/0'/$chain/$index");
|
||||
default:
|
||||
throw Exception("DerivePathType must not be null.");
|
||||
throw Exception("DerivePathType unsupported");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,7 +137,8 @@ bip32.BIP32 getBip32RootWrapper(Tuple2<String, NetworkType> args) {
|
|||
return getBip32Root(args.item1, args.item2);
|
||||
}
|
||||
|
||||
class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
||||
class LitecoinWallet extends CoinServiceAPI
|
||||
with WalletCache, WalletDB, ElectrumXParsing {
|
||||
static const integrationTestFlag =
|
||||
bool.fromEnvironment("IS_INTEGRATION_TEST");
|
||||
|
||||
|
@ -193,7 +193,7 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.subTypeEqualTo(isar_models.AddressSubType.receiving)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()) ??
|
||||
await _generateAddressForChain(0, 0, DerivePathType.bip84);
|
||||
await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
Future<String> get currentChangeAddress async =>
|
||||
(await _currentChangeAddress).value;
|
||||
|
@ -206,7 +206,7 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.subTypeEqualTo(isar_models.AddressSubType.change)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()) ??
|
||||
await _generateAddressForChain(1, 0, DerivePathType.bip84);
|
||||
await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
@override
|
||||
Future<void> exit() async {
|
||||
|
@ -241,6 +241,14 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
@ -421,7 +429,7 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
addrType = isar_models.AddressType.p2wpkh;
|
||||
break;
|
||||
default:
|
||||
throw Exception("No Path type $type exists");
|
||||
throw Exception("DerivePathType unsupported");
|
||||
}
|
||||
|
||||
final address = isar_models.Address(
|
||||
|
@ -509,6 +517,7 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
required String mnemonic,
|
||||
int maxUnusedAddressGap = 20,
|
||||
int maxNumberOfIndexesToCheck = 1000,
|
||||
bool isRescan = false,
|
||||
}) async {
|
||||
longMutex = true;
|
||||
|
||||
|
@ -682,14 +691,25 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
p2wpkhChangeAddressArray.add(address);
|
||||
}
|
||||
|
||||
await db.putAddresses([
|
||||
...p2wpkhReceiveAddressArray,
|
||||
...p2wpkhChangeAddressArray,
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
...p2shReceiveAddressArray,
|
||||
...p2shChangeAddressArray,
|
||||
]);
|
||||
if (isRescan) {
|
||||
await db.updateOrPutAddresses([
|
||||
...p2wpkhReceiveAddressArray,
|
||||
...p2wpkhChangeAddressArray,
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
...p2shReceiveAddressArray,
|
||||
...p2shChangeAddressArray,
|
||||
]);
|
||||
} else {
|
||||
await db.putAddresses([
|
||||
...p2wpkhReceiveAddressArray,
|
||||
...p2wpkhChangeAddressArray,
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
...p2shReceiveAddressArray,
|
||||
...p2shChangeAddressArray,
|
||||
]);
|
||||
}
|
||||
|
||||
await _updateUTXOs();
|
||||
|
||||
|
@ -1194,46 +1214,31 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
// transactions locally in a good way
|
||||
@override
|
||||
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
|
||||
// final priceData =
|
||||
// await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
|
||||
// Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
||||
// final locale =
|
||||
// Platform.isWindows ? "en_US" : await Devicelocale.currentLocale;
|
||||
// final String worthNow = Format.localizedStringAsFixed(
|
||||
// value:
|
||||
// ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) /
|
||||
// Decimal.fromInt(Constants.satsPerCoin(coin)))
|
||||
// .toDecimal(scaleOnInfinitePrecision: 2),
|
||||
// decimalPlaces: 2,
|
||||
// locale: locale!);
|
||||
//
|
||||
// final tx = models.Transaction(
|
||||
// txid: txData["txid"] as String,
|
||||
// confirmedStatus: false,
|
||||
// timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
// txType: "Sent",
|
||||
// amount: txData["recipientAmt"] as int,
|
||||
// worthNow: worthNow,
|
||||
// worthAtBlockTimestamp: worthNow,
|
||||
// fees: txData["fee"] as int,
|
||||
// inputSize: 0,
|
||||
// outputSize: 0,
|
||||
// inputs: [],
|
||||
// outputs: [],
|
||||
// address: txData["address"] as String,
|
||||
// height: -1,
|
||||
// confirmations: 0,
|
||||
// );
|
||||
//
|
||||
// if (cachedTxData == null) {
|
||||
// final data = await _refreshTransactions();
|
||||
// _transactionData = Future(() => data);
|
||||
// }
|
||||
//
|
||||
// final transactions = cachedTxData!.getAllTransactions();
|
||||
// transactions[tx.txid] = tx;
|
||||
// cachedTxData = models.TransactionData.fromMap(transactions);
|
||||
// _transactionData = Future(() => cachedTxData!);
|
||||
final transaction = isar_models.Transaction(
|
||||
walletId: walletId,
|
||||
txid: txData["txid"] as String,
|
||||
timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
type: isar_models.TransactionType.outgoing,
|
||||
subType: isar_models.TransactionSubType.none,
|
||||
amount: txData["recipientAmt"] as int,
|
||||
fee: txData["fee"] as int,
|
||||
height: null,
|
||||
isCancelled: false,
|
||||
isLelantus: false,
|
||||
otherData: null,
|
||||
slateId: null,
|
||||
);
|
||||
|
||||
final address = txData["address"] is String
|
||||
? await db.getAddress(walletId, txData["address"] as String)
|
||||
: null;
|
||||
|
||||
await db.addNewTransactionData(
|
||||
[
|
||||
Tuple4(transaction, [], [], address),
|
||||
],
|
||||
walletId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1532,6 +1537,8 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.address!;
|
||||
addrType = isar_models.AddressType.p2wpkh;
|
||||
break;
|
||||
default:
|
||||
throw Exception("DerivePathType unsupported");
|
||||
}
|
||||
|
||||
// add generated address & info to derivations
|
||||
|
@ -1578,6 +1585,8 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip84:
|
||||
type = isar_models.AddressType.p2wpkh;
|
||||
break;
|
||||
default:
|
||||
throw Exception("DerivePathType unsupported");
|
||||
}
|
||||
address = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1605,6 +1614,8 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip84:
|
||||
key = "${walletId}_${chainId}DerivationsP2WPKH";
|
||||
break;
|
||||
default:
|
||||
throw Exception("DerivePathType unsupported");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
@ -1776,7 +1787,7 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.clear();
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
|
||||
|
@ -1891,7 +1902,7 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = await _generateAddressForChain(
|
||||
0, newReceivingIndex, DerivePathType.bip84);
|
||||
0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
final existing = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1910,7 +1921,7 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathType.bip84}): $e\n$s",
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -1930,7 +1941,7 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new change address
|
||||
final newChangeAddress = await _generateAddressForChain(
|
||||
1, newChangeIndex, DerivePathType.bip84);
|
||||
1, newChangeIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
final existing = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1949,12 +1960,12 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
} on SocketException catch (se, s) {
|
||||
Logging.instance.log(
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathType.bip84}): $se\n$s",
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s",
|
||||
level: LogLevel.Error);
|
||||
return;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathType.bip84}): $e\n$s",
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -2349,7 +2360,7 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
await _getCurrentAddressForChain(1, DerivePathType.bip84),
|
||||
await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)),
|
||||
],
|
||||
satoshiAmounts: [
|
||||
satoshiAmountToSend,
|
||||
|
@ -2388,8 +2399,8 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
feeForTwoOutputs) {
|
||||
// generate new change address if current change address has been used
|
||||
await _checkChangeAddressForTransactions();
|
||||
final String newChangeAddress =
|
||||
await _getCurrentAddressForChain(1, DerivePathType.bip84);
|
||||
final String newChangeAddress = await _getCurrentAddressForChain(
|
||||
1, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
int feeBeingPaid =
|
||||
satoshisBeingUsed - satoshiAmountToSend - changeOutputSize;
|
||||
|
@ -2595,6 +2606,8 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip84:
|
||||
addressesP2WPKH.add(address);
|
||||
break;
|
||||
default:
|
||||
throw Exception("DerivePathType unsupported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2879,6 +2892,7 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
mnemonic: mnemonic!,
|
||||
maxUnusedAddressGap: maxUnusedAddressGap,
|
||||
maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
|
||||
isRescan: true,
|
||||
);
|
||||
|
||||
longMutex = false;
|
||||
|
@ -3345,7 +3359,7 @@ class LitecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = await _generateAddressForChain(
|
||||
0, newReceivingIndex, DerivePathType.bip84);
|
||||
0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
// Add that new receiving address
|
||||
await db.putAddress(newReceivingAddress);
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:stackwallet/services/coins/coin_service.dart';
|
|||
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
||||
|
@ -109,8 +110,15 @@ class Manager with ChangeNotifier {
|
|||
try {
|
||||
final txid = await _currentWallet.confirmSend(txData: txData);
|
||||
|
||||
txData["txid"] = txid;
|
||||
await _currentWallet.updateSentCachedTxData(txData);
|
||||
try {
|
||||
txData["txid"] = txid;
|
||||
await _currentWallet.updateSentCachedTxData(txData);
|
||||
} catch (e, s) {
|
||||
// do not rethrow as that would get handled as a send failure further up
|
||||
// also this is not critical code and transaction should show up on \
|
||||
// refresh regardless
|
||||
Logging.instance.log("$e\n$s", level: LogLevel.Warning);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
return txid;
|
||||
|
@ -214,4 +222,6 @@ class Manager with ChangeNotifier {
|
|||
}
|
||||
|
||||
int get currentHeight => _currentWallet.storedChainHeight;
|
||||
|
||||
bool get hasPaynymSupport => _currentWallet is PaynymWalletInterface;
|
||||
}
|
||||
|
|
|
@ -17,13 +17,13 @@ import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
|||
import 'package:stackwallet/models/balance.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||
import 'package:stackwallet/services/coins/coin_service.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
import 'package:stackwallet/services/mixins/electrum_x_parsing.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
||||
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
||||
import 'package:stackwallet/services/node_service.dart';
|
||||
|
@ -33,6 +33,7 @@ import 'package:stackwallet/utilities/assets.dart';
|
|||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
|
@ -50,8 +51,6 @@ const String GENESIS_HASH_MAINNET =
|
|||
const String GENESIS_HASH_TESTNET =
|
||||
"00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008";
|
||||
|
||||
enum DerivePathType { bip44, bip49, bip84 }
|
||||
|
||||
bip32.BIP32 getBip32Node(
|
||||
int chain,
|
||||
int index,
|
||||
|
@ -135,7 +134,8 @@ bip32.BIP32 getBip32RootWrapper(Tuple2<String, NetworkType> args) {
|
|||
return getBip32Root(args.item1, args.item2);
|
||||
}
|
||||
|
||||
class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
||||
class NamecoinWallet extends CoinServiceAPI
|
||||
with WalletCache, WalletDB, ElectrumXParsing {
|
||||
static const integrationTestFlag =
|
||||
bool.fromEnvironment("IS_INTEGRATION_TEST");
|
||||
|
||||
|
@ -188,7 +188,7 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.subTypeEqualTo(isar_models.AddressSubType.receiving)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()) ??
|
||||
await _generateAddressForChain(0, 0, DerivePathType.bip84);
|
||||
await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
Future<String> get currentChangeAddress async =>
|
||||
(await _currentChangeAddress).value;
|
||||
|
@ -201,7 +201,7 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.subTypeEqualTo(isar_models.AddressSubType.change)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()) ??
|
||||
await _generateAddressForChain(1, 0, DerivePathType.bip84);
|
||||
await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
@override
|
||||
Future<void> exit() async {
|
||||
|
@ -236,6 +236,14 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
@ -411,7 +419,7 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
addrType = isar_models.AddressType.p2wpkh;
|
||||
break;
|
||||
default:
|
||||
throw Exception("No Path type $type exists");
|
||||
throw Exception("DerivePathType $type not supported");
|
||||
}
|
||||
|
||||
final address = isar_models.Address(
|
||||
|
@ -499,6 +507,7 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
required String mnemonic,
|
||||
int maxUnusedAddressGap = 20,
|
||||
int maxNumberOfIndexesToCheck = 1000,
|
||||
bool isRescan = false,
|
||||
}) async {
|
||||
longMutex = true;
|
||||
|
||||
|
@ -672,14 +681,25 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
p2wpkhChangeAddressArray.add(address);
|
||||
}
|
||||
|
||||
await db.putAddresses([
|
||||
...p2wpkhReceiveAddressArray,
|
||||
...p2wpkhChangeAddressArray,
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
...p2shReceiveAddressArray,
|
||||
...p2shChangeAddressArray,
|
||||
]);
|
||||
if (isRescan) {
|
||||
await db.updateOrPutAddresses([
|
||||
...p2wpkhReceiveAddressArray,
|
||||
...p2wpkhChangeAddressArray,
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
...p2shReceiveAddressArray,
|
||||
...p2shChangeAddressArray,
|
||||
]);
|
||||
} else {
|
||||
await db.putAddresses([
|
||||
...p2wpkhReceiveAddressArray,
|
||||
...p2wpkhChangeAddressArray,
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
...p2shReceiveAddressArray,
|
||||
...p2shChangeAddressArray,
|
||||
]);
|
||||
}
|
||||
|
||||
await _updateUTXOs();
|
||||
|
||||
|
@ -1183,46 +1203,31 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
// transactions locally in a good way
|
||||
@override
|
||||
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
|
||||
// final priceData =
|
||||
// await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
|
||||
// Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
||||
// final locale =
|
||||
// Platform.isWindows ? "en_US" : await Devicelocale.currentLocale;
|
||||
// final String worthNow = Format.localizedStringAsFixed(
|
||||
// value:
|
||||
// ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) /
|
||||
// Decimal.fromInt(Constants.satsPerCoin(coin)))
|
||||
// .toDecimal(scaleOnInfinitePrecision: 2),
|
||||
// decimalPlaces: 2,
|
||||
// locale: locale!);
|
||||
//
|
||||
// final tx = models.Transaction(
|
||||
// txid: txData["txid"] as String,
|
||||
// confirmedStatus: false,
|
||||
// timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
// txType: "Sent",
|
||||
// amount: txData["recipientAmt"] as int,
|
||||
// worthNow: worthNow,
|
||||
// worthAtBlockTimestamp: worthNow,
|
||||
// fees: txData["fee"] as int,
|
||||
// inputSize: 0,
|
||||
// outputSize: 0,
|
||||
// inputs: [],
|
||||
// outputs: [],
|
||||
// address: txData["address"] as String,
|
||||
// height: -1,
|
||||
// confirmations: 0,
|
||||
// );
|
||||
//
|
||||
// if (cachedTxData == null) {
|
||||
// final data = await _refreshTransactions();
|
||||
// _transactionData = Future(() => data);
|
||||
// }
|
||||
//
|
||||
// final transactions = cachedTxData!.getAllTransactions();
|
||||
// transactions[tx.txid] = tx;
|
||||
// cachedTxData = models.TransactionData.fromMap(transactions);
|
||||
// _transactionData = Future(() => cachedTxData!);
|
||||
final transaction = isar_models.Transaction(
|
||||
walletId: walletId,
|
||||
txid: txData["txid"] as String,
|
||||
timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
type: isar_models.TransactionType.outgoing,
|
||||
subType: isar_models.TransactionSubType.none,
|
||||
amount: txData["recipientAmt"] as int,
|
||||
fee: txData["fee"] as int,
|
||||
height: null,
|
||||
isCancelled: false,
|
||||
isLelantus: false,
|
||||
otherData: null,
|
||||
slateId: null,
|
||||
);
|
||||
|
||||
final address = txData["address"] is String
|
||||
? await db.getAddress(walletId, txData["address"] as String)
|
||||
: null;
|
||||
|
||||
await db.addNewTransactionData(
|
||||
[
|
||||
Tuple4(transaction, [], [], address),
|
||||
],
|
||||
walletId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1418,17 +1423,21 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
Logging.instance
|
||||
.log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info);
|
||||
if (!integrationTestFlag) {
|
||||
final features = await electrumXClient.getServerFeatures();
|
||||
Logging.instance.log("features: $features", level: LogLevel.Info);
|
||||
switch (coin) {
|
||||
case Coin.namecoin:
|
||||
if (features['genesis_hash'] != GENESIS_HASH_MAINNET) {
|
||||
throw Exception("genesis hash does not match main net!");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw Exception(
|
||||
"Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}");
|
||||
try {
|
||||
final features = await electrumXClient.getServerFeatures();
|
||||
Logging.instance.log("features: $features", level: LogLevel.Info);
|
||||
switch (coin) {
|
||||
case Coin.namecoin:
|
||||
if (features['genesis_hash'] != GENESIS_HASH_MAINNET) {
|
||||
throw Exception("genesis hash does not match main net!");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw Exception(
|
||||
"Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}");
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e/n$s", level: LogLevel.Info);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1509,6 +1518,8 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.address!;
|
||||
addrType = isar_models.AddressType.p2wpkh;
|
||||
break;
|
||||
default:
|
||||
throw Exception("DerivePathType must not be null.");
|
||||
}
|
||||
|
||||
// add generated address & info to derivations
|
||||
|
@ -1555,6 +1566,9 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip84:
|
||||
type = isar_models.AddressType.p2wpkh;
|
||||
break;
|
||||
default:
|
||||
throw Exception(
|
||||
"DerivePathType null or unsupported (${DerivePathType.bip44})");
|
||||
}
|
||||
address = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1582,6 +1596,8 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip84:
|
||||
key = "${walletId}_${chainId}DerivationsP2WPKH";
|
||||
break;
|
||||
default:
|
||||
throw Exception("DerivePathType $derivePathType not supported");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
@ -1755,7 +1771,7 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.clear();
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
|
||||
|
@ -1873,7 +1889,7 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = await _generateAddressForChain(
|
||||
0, newReceivingIndex, DerivePathType.bip84);
|
||||
0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
final existing = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1892,7 +1908,7 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathType.bip84}): $e\n$s",
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -1912,7 +1928,7 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new change address
|
||||
final newChangeAddress = await _generateAddressForChain(
|
||||
1, newChangeIndex, DerivePathType.bip84);
|
||||
1, newChangeIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
final existing = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1932,12 +1948,12 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
} on SocketException catch (se, s) {
|
||||
Logging.instance.log(
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathType.bip84}): $se\n$s",
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s",
|
||||
level: LogLevel.Error);
|
||||
return;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathType.bip84}): $e\n$s",
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -2336,7 +2352,7 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
await _getCurrentAddressForChain(1, DerivePathType.bip84),
|
||||
await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)),
|
||||
],
|
||||
satoshiAmounts: [
|
||||
satoshiAmountToSend,
|
||||
|
@ -2375,8 +2391,8 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
feeForTwoOutputs) {
|
||||
// generate new change address if current change address has been used
|
||||
await _checkChangeAddressForTransactions();
|
||||
final String newChangeAddress =
|
||||
await _getCurrentAddressForChain(1, DerivePathType.bip84);
|
||||
final String newChangeAddress = await _getCurrentAddressForChain(
|
||||
1, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
int feeBeingPaid =
|
||||
satoshisBeingUsed - satoshiAmountToSend - changeOutputSize;
|
||||
|
@ -2869,6 +2885,7 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
mnemonic: mnemonic!,
|
||||
maxUnusedAddressGap: maxUnusedAddressGap,
|
||||
maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
|
||||
isRescan: true,
|
||||
);
|
||||
|
||||
longMutex = false;
|
||||
|
@ -3336,7 +3353,7 @@ class NamecoinWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = await _generateAddressForChain(
|
||||
0, newReceivingIndex, DerivePathType.bip84);
|
||||
0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
// Add that new receiving address
|
||||
await db.putAddress(newReceivingAddress);
|
||||
|
|
|
@ -32,6 +32,7 @@ import 'package:stackwallet/utilities/assets.dart';
|
|||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
|
@ -48,8 +49,6 @@ const String GENESIS_HASH_MAINNET =
|
|||
const String GENESIS_HASH_TESTNET =
|
||||
"0000594ada5310b367443ee0afd4fa3d0bbd5850ea4e33cdc7d6a904a7ec7c90";
|
||||
|
||||
enum DerivePathType { bip44, bip84 }
|
||||
|
||||
bip32.BIP32 getBip32Node(
|
||||
int chain,
|
||||
int index,
|
||||
|
@ -96,7 +95,7 @@ bip32.BIP32 getBip32NodeFromRoot(
|
|||
case DerivePathType.bip84:
|
||||
return root.derivePath("m/84'/$coinType'/0'/$chain/$index");
|
||||
default:
|
||||
throw Exception("DerivePathType must not be null.");
|
||||
throw Exception("DerivePathType $derivePathType not supported");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,7 +183,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.subTypeEqualTo(isar_models.AddressSubType.receiving)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()) ??
|
||||
await _generateAddressForChain(0, 0, DerivePathType.bip84);
|
||||
await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
Future<String> get currentChangeAddress async =>
|
||||
(await _currentChangeAddress).value;
|
||||
|
@ -197,7 +196,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
.subTypeEqualTo(isar_models.AddressSubType.change)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()) ??
|
||||
await _generateAddressForChain(1, 0, DerivePathType.bip84);
|
||||
await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
@override
|
||||
Future<void> exit() async {
|
||||
|
@ -232,6 +231,14 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
final result = await _electrumXClient.getBlockHeadTip();
|
||||
final height = result["height"] as int;
|
||||
await updateCachedChainHeight(height);
|
||||
if (height > storedChainHeight) {
|
||||
GlobalEventBus.instance.fire(
|
||||
UpdatedInBackgroundEvent(
|
||||
"Updated current chain height in $walletId $walletName!",
|
||||
walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
return height;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Exception caught in chainHeight: $e\n$s",
|
||||
|
@ -394,7 +401,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
addrType = isar_models.AddressType.p2wpkh;
|
||||
break;
|
||||
default:
|
||||
throw Exception("No Path type $type exists");
|
||||
throw Exception("DerivePathType $type not supported");
|
||||
}
|
||||
|
||||
final address = isar_models.Address(
|
||||
|
@ -482,6 +489,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
required String mnemonic,
|
||||
int maxUnusedAddressGap = 20,
|
||||
int maxNumberOfIndexesToCheck = 1000,
|
||||
bool isRescan = false,
|
||||
}) async {
|
||||
longMutex = true;
|
||||
|
||||
|
@ -607,12 +615,21 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
p2wpkhChangeAddressArray.add(address);
|
||||
}
|
||||
|
||||
await db.putAddresses([
|
||||
...p2wpkhReceiveAddressArray,
|
||||
...p2wpkhChangeAddressArray,
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
]);
|
||||
if (isRescan) {
|
||||
await db.updateOrPutAddresses([
|
||||
...p2wpkhReceiveAddressArray,
|
||||
...p2wpkhChangeAddressArray,
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
]);
|
||||
} else {
|
||||
await db.putAddresses([
|
||||
...p2wpkhReceiveAddressArray,
|
||||
...p2wpkhChangeAddressArray,
|
||||
...p2pkhReceiveAddressArray,
|
||||
...p2pkhChangeAddressArray,
|
||||
]);
|
||||
}
|
||||
|
||||
await _updateUTXOs();
|
||||
|
||||
|
@ -1114,46 +1131,31 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
// transactions locally in a good way
|
||||
@override
|
||||
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) async {
|
||||
// final priceData =
|
||||
// await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
|
||||
// Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
|
||||
// final locale =
|
||||
// Platform.isWindows ? "en_US" : await Devicelocale.currentLocale;
|
||||
// final String worthNow = Format.localizedStringAsFixed(
|
||||
// value:
|
||||
// ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) /
|
||||
// Decimal.fromInt(Constants.satsPerCoin(coin)))
|
||||
// .toDecimal(scaleOnInfinitePrecision: 2),
|
||||
// decimalPlaces: 2,
|
||||
// locale: locale!);
|
||||
//
|
||||
// final tx = models.Transaction(
|
||||
// txid: txData["txid"] as String,
|
||||
// confirmedStatus: false,
|
||||
// timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
// txType: "Sent",
|
||||
// amount: txData["recipientAmt"] as int,
|
||||
// worthNow: worthNow,
|
||||
// worthAtBlockTimestamp: worthNow,
|
||||
// fees: txData["fee"] as int,
|
||||
// inputSize: 0,
|
||||
// outputSize: 0,
|
||||
// inputs: [],
|
||||
// outputs: [],
|
||||
// address: txData["address"] as String,
|
||||
// height: -1,
|
||||
// confirmations: 0,
|
||||
// );
|
||||
//
|
||||
// if (cachedTxData == null) {
|
||||
// final data = await _refreshTransactions();
|
||||
// _transactionData = Future(() => data);
|
||||
// } else {
|
||||
// final transactions = cachedTxData!.getAllTransactions();
|
||||
// transactions[tx.txid] = tx;
|
||||
// cachedTxData = models.TransactionData.fromMap(transactions);
|
||||
// _transactionData = Future(() => cachedTxData!);
|
||||
// }
|
||||
final transaction = isar_models.Transaction(
|
||||
walletId: walletId,
|
||||
txid: txData["txid"] as String,
|
||||
timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
type: isar_models.TransactionType.outgoing,
|
||||
subType: isar_models.TransactionSubType.none,
|
||||
amount: txData["recipientAmt"] as int,
|
||||
fee: txData["fee"] as int,
|
||||
height: null,
|
||||
isCancelled: false,
|
||||
isLelantus: false,
|
||||
otherData: null,
|
||||
slateId: null,
|
||||
);
|
||||
|
||||
final address = txData["address"] is String
|
||||
? await db.getAddress(walletId, txData["address"] as String)
|
||||
: null;
|
||||
|
||||
await db.addNewTransactionData(
|
||||
[
|
||||
Tuple4(transaction, [], [], address),
|
||||
],
|
||||
walletId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1336,17 +1338,21 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
Logging.instance
|
||||
.log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info);
|
||||
if (!integrationTestFlag) {
|
||||
final features = await electrumXClient.getServerFeatures();
|
||||
Logging.instance.log("features: $features", level: LogLevel.Info);
|
||||
switch (coin) {
|
||||
case Coin.particl:
|
||||
if (features['genesis_hash'] != GENESIS_HASH_MAINNET) {
|
||||
throw Exception("genesis hash does not match main net!");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw Exception(
|
||||
"Attempted to generate a ParticlWallet using a non particl coin type: ${coin.name}");
|
||||
try {
|
||||
final features = await electrumXClient.getServerFeatures();
|
||||
Logging.instance.log("features: $features", level: LogLevel.Info);
|
||||
switch (coin) {
|
||||
case Coin.particl:
|
||||
if (features['genesis_hash'] != GENESIS_HASH_MAINNET) {
|
||||
throw Exception("genesis hash does not match main net!");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw Exception(
|
||||
"Attempted to generate a ParticlWallet using a non particl coin type: ${coin.name}");
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e/n$s", level: LogLevel.Info);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1407,6 +1413,8 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
address = P2WPKH(network: _network, data: data).data.address!;
|
||||
addrType = isar_models.AddressType.p2wpkh;
|
||||
break;
|
||||
default:
|
||||
throw Exception("DerivePathType $derivePathType not supported");
|
||||
}
|
||||
|
||||
// add generated address & info to derivations
|
||||
|
@ -1450,6 +1458,8 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip84:
|
||||
type = isar_models.AddressType.p2wpkh;
|
||||
break;
|
||||
default:
|
||||
throw Exception("DerivePathType $derivePathType not supported");
|
||||
}
|
||||
address = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1475,6 +1485,8 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip84:
|
||||
key = "${walletId}_${chainId}DerivationsP2WPKH";
|
||||
break;
|
||||
default:
|
||||
throw Exception("DerivePathType $derivePathType not supported");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
@ -1646,7 +1658,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// TODO move this out of here and into IDB
|
||||
await db.isar.writeTxn(() async {
|
||||
await db.isar.utxos.clear();
|
||||
await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll();
|
||||
await db.isar.utxos.putAll(outputArray);
|
||||
});
|
||||
|
||||
|
@ -1761,7 +1773,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = await _generateAddressForChain(
|
||||
0, newReceivingIndex, DerivePathType.bip84);
|
||||
0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
final existing = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1780,7 +1792,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathType.bip84}): $e\n$s",
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -1800,7 +1812,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new change address
|
||||
final newChangeAddress = await _generateAddressForChain(
|
||||
1, newChangeIndex, DerivePathType.bip84);
|
||||
1, newChangeIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
final existing = await db
|
||||
.getAddresses(walletId)
|
||||
|
@ -1819,12 +1831,12 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
}
|
||||
} on SocketException catch (se, s) {
|
||||
Logging.instance.log(
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathType.bip84}): $se\n$s",
|
||||
"SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s",
|
||||
level: LogLevel.Error);
|
||||
return;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathType.bip84}): $e\n$s",
|
||||
"Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -2189,7 +2201,6 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
Logging.instance.log(s.toString(), level: LogLevel.Warning);
|
||||
}
|
||||
// Logging.instance.log("output is transparent", level: LogLevel.Info);
|
||||
|
||||
} else if (output.containsKey('ct_fee') as bool) {
|
||||
// or type: data
|
||||
// TODO handle CT tx
|
||||
|
@ -2501,7 +2512,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
utxoSigningData: utxoSigningData,
|
||||
recipients: [
|
||||
_recipientAddress,
|
||||
await _getCurrentAddressForChain(1, DerivePathType.bip84),
|
||||
await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)),
|
||||
],
|
||||
satoshiAmounts: [
|
||||
satoshiAmountToSend,
|
||||
|
@ -2540,8 +2551,8 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
feeForTwoOutputs) {
|
||||
// generate new change address if current change address has been used
|
||||
await _checkChangeAddressForTransactions();
|
||||
final String newChangeAddress =
|
||||
await _getCurrentAddressForChain(1, DerivePathType.bip84);
|
||||
final String newChangeAddress = await _getCurrentAddressForChain(
|
||||
1, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
int feeBeingPaid =
|
||||
satoshisBeingUsed - satoshiAmountToSend - changeOutputSize;
|
||||
|
@ -2743,6 +2754,9 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
case DerivePathType.bip84:
|
||||
addressesP2WPKH.add(address);
|
||||
break;
|
||||
default:
|
||||
throw Exception(
|
||||
"DerivePathType ${addressType(address: address)} not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2967,6 +2981,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
mnemonic: mnemonic!,
|
||||
maxUnusedAddressGap: maxUnusedAddressGap,
|
||||
maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck,
|
||||
isRescan: true,
|
||||
);
|
||||
|
||||
longMutex = false;
|
||||
|
@ -3328,7 +3343,7 @@ class ParticlWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
|||
|
||||
// Use new index to derive a new receiving address
|
||||
final newReceivingAddress = await _generateAddressForChain(
|
||||
0, newReceivingIndex, DerivePathType.bip84);
|
||||
0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin));
|
||||
|
||||
// Add that new receiving address
|
||||
await db.putAddress(newReceivingAddress);
|
||||
|
|
224
lib/services/mixins/electrum_x_parsing.dart
Normal file
|
@ -0,0 +1,224 @@
|
|||
import 'package:bip47/src/util.dart';
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
mixin ElectrumXParsing {
|
||||
Future<Tuple4<Transaction, List<Output>, List<Input>, Address>>
|
||||
parseTransaction(
|
||||
Map<String, dynamic> txData,
|
||||
dynamic electrumxClient,
|
||||
List<Address> myAddresses,
|
||||
Coin coin,
|
||||
int minConfirms,
|
||||
String walletId,
|
||||
) async {
|
||||
Set<String> receivingAddresses = myAddresses
|
||||
.where((e) =>
|
||||
e.subType == AddressSubType.receiving ||
|
||||
e.subType == AddressSubType.paynymReceive ||
|
||||
e.subType == AddressSubType.paynymNotification)
|
||||
.map((e) => e.value)
|
||||
.toSet();
|
||||
Set<String> changeAddresses = myAddresses
|
||||
.where((e) => e.subType == AddressSubType.change)
|
||||
.map((e) => e.value)
|
||||
.toSet();
|
||||
|
||||
Set<String> inputAddresses = {};
|
||||
Set<String> outputAddresses = {};
|
||||
|
||||
int totalInputValue = 0;
|
||||
int totalOutputValue = 0;
|
||||
|
||||
int amountSentFromWallet = 0;
|
||||
int amountReceivedInWallet = 0;
|
||||
int changeAmount = 0;
|
||||
|
||||
// parse inputs
|
||||
for (final input in txData["vin"] as List) {
|
||||
final prevTxid = input["txid"] as String;
|
||||
final prevOut = input["vout"] as int;
|
||||
|
||||
// fetch input tx to get address
|
||||
final inputTx = await electrumxClient.getTransaction(
|
||||
txHash: prevTxid,
|
||||
coin: coin,
|
||||
);
|
||||
|
||||
for (final output in inputTx["vout"] as List) {
|
||||
// check matching output
|
||||
if (prevOut == output["n"]) {
|
||||
// get value
|
||||
final value = Format.decimalAmountToSatoshis(
|
||||
Decimal.parse(output["value"].toString()),
|
||||
coin,
|
||||
);
|
||||
|
||||
// add value to total
|
||||
totalInputValue += value;
|
||||
|
||||
// get input(prevOut) address
|
||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
|
||||
if (address != null) {
|
||||
inputAddresses.add(address);
|
||||
|
||||
// if input was from my wallet, add value to amount sent
|
||||
if (receivingAddresses.contains(address) ||
|
||||
changeAddresses.contains(address)) {
|
||||
amountSentFromWallet += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse outputs
|
||||
for (final output in txData["vout"] as List) {
|
||||
// get value
|
||||
final value = Format.decimalAmountToSatoshis(
|
||||
Decimal.parse(output["value"].toString()),
|
||||
coin,
|
||||
);
|
||||
|
||||
// add value to total
|
||||
totalOutputValue += value;
|
||||
|
||||
// get output address
|
||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
if (address != null) {
|
||||
outputAddresses.add(address);
|
||||
|
||||
// if output was to my wallet, add value to amount received
|
||||
if (receivingAddresses.contains(address)) {
|
||||
amountReceivedInWallet += value;
|
||||
} else if (changeAddresses.contains(address)) {
|
||||
changeAmount += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final mySentFromAddresses = [
|
||||
...receivingAddresses.intersection(inputAddresses),
|
||||
...changeAddresses.intersection(inputAddresses)
|
||||
];
|
||||
final myReceivedOnAddresses =
|
||||
receivingAddresses.intersection(outputAddresses);
|
||||
final myChangeReceivedOnAddresses =
|
||||
changeAddresses.intersection(outputAddresses);
|
||||
|
||||
final fee = totalInputValue - totalOutputValue;
|
||||
|
||||
// this is the address initially used to fetch the txid
|
||||
Address transactionAddress = txData["address"] as Address;
|
||||
|
||||
TransactionType type;
|
||||
int amount;
|
||||
if (mySentFromAddresses.isNotEmpty && myReceivedOnAddresses.isNotEmpty) {
|
||||
// tx is sent to self
|
||||
type = TransactionType.sentToSelf;
|
||||
|
||||
// should be 0
|
||||
amount =
|
||||
amountSentFromWallet - amountReceivedInWallet - fee - changeAmount;
|
||||
} else if (mySentFromAddresses.isNotEmpty) {
|
||||
// outgoing tx
|
||||
type = TransactionType.outgoing;
|
||||
amount = amountSentFromWallet - changeAmount - fee;
|
||||
|
||||
final possible =
|
||||
outputAddresses.difference(myChangeReceivedOnAddresses).first;
|
||||
|
||||
if (transactionAddress.value != possible) {
|
||||
transactionAddress = Address(
|
||||
walletId: walletId,
|
||||
value: possible,
|
||||
derivationIndex: -1,
|
||||
subType: AddressSubType.nonWallet,
|
||||
type: AddressType.nonWallet,
|
||||
publicKey: [],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// incoming tx
|
||||
type = TransactionType.incoming;
|
||||
amount = amountReceivedInWallet;
|
||||
}
|
||||
|
||||
List<Output> outs = [];
|
||||
List<Input> ins = [];
|
||||
|
||||
for (final json in txData["vin"] as List) {
|
||||
bool isCoinBase = json['coinbase'] != null;
|
||||
final input = Input(
|
||||
walletId: walletId,
|
||||
txid: json['txid'] as String,
|
||||
vout: json['vout'] as int? ?? -1,
|
||||
scriptSig: json['scriptSig']?['hex'] as String?,
|
||||
scriptSigAsm: json['scriptSig']?['asm'] as String?,
|
||||
isCoinbase: isCoinBase ? isCoinBase : json['is_coinbase'] as bool?,
|
||||
sequence: json['sequence'] as int?,
|
||||
innerRedeemScriptAsm: json['innerRedeemscriptAsm'] as String?,
|
||||
);
|
||||
ins.add(input);
|
||||
}
|
||||
|
||||
for (final json in txData["vout"] as List) {
|
||||
final output = Output(
|
||||
walletId: walletId,
|
||||
scriptPubKey: json['scriptPubKey']?['hex'] as String?,
|
||||
scriptPubKeyAsm: json['scriptPubKey']?['asm'] as String?,
|
||||
scriptPubKeyType: json['scriptPubKey']?['type'] as String?,
|
||||
scriptPubKeyAddress:
|
||||
json["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
json['scriptPubKey']?['type'] as String? ??
|
||||
"",
|
||||
value: Format.decimalAmountToSatoshis(
|
||||
Decimal.parse(json["value"].toString()),
|
||||
coin,
|
||||
),
|
||||
);
|
||||
outs.add(output);
|
||||
}
|
||||
|
||||
TransactionSubType txSubType = TransactionSubType.none;
|
||||
if (this is PaynymWalletInterface && outs.length > 1 && ins.isNotEmpty) {
|
||||
for (int i = 0; i < outs.length; i++) {
|
||||
List<String>? scriptChunks = outs[i].scriptPubKeyAsm?.split(" ");
|
||||
if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") {
|
||||
final blindedPaymentCode = scriptChunks![1];
|
||||
final bytes = blindedPaymentCode.fromHex;
|
||||
|
||||
// https://en.bitcoin.it/wiki/BIP_0047#Sending
|
||||
if (bytes.length == 80 && bytes.first == 1) {
|
||||
txSubType = TransactionSubType.bip47Notification;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final tx = Transaction(
|
||||
walletId: walletId,
|
||||
txid: txData["txid"] as String,
|
||||
timestamp: txData["blocktime"] as int? ??
|
||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||
type: type,
|
||||
subType: txSubType,
|
||||
amount: amount,
|
||||
fee: fee,
|
||||
height: txData["height"] as int?,
|
||||
isCancelled: false,
|
||||
isLelantus: false,
|
||||
slateId: null,
|
||||
otherData: null,
|
||||
);
|
||||
|
||||
return Tuple4(tx, outs, ins, transactionAddress);
|
||||
}
|
||||
}
|
1257
lib/services/mixins/paynym_wallet_interface.dart
Normal file
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||
import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart';
|
||||
import 'package:stackwallet/hive/db.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
||||
import 'package:stackwallet/models/notification_model.dart';
|
||||
|
@ -169,12 +170,14 @@ class NotificationsService extends ChangeNotifier {
|
|||
}
|
||||
|
||||
// replaces the current notification with the updated one
|
||||
add(updatedNotification, true);
|
||||
await add(updatedNotification, true);
|
||||
}
|
||||
} else {
|
||||
// TODO: check non electrumx coins
|
||||
}
|
||||
}
|
||||
} on NoSuchTransactionException catch (e, s) {
|
||||
await _deleteWatchedTxNotification(notification);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("$e $s", level: LogLevel.Error);
|
||||
}
|
||||
|
|
|
@ -54,4 +54,25 @@ class TransactionNotificationTracker {
|
|||
key: "notifiedConfirmedTransactions",
|
||||
value: notifiedConfirmedTransactions);
|
||||
}
|
||||
|
||||
Future<void> deleteTransaction(String txid) async {
|
||||
final notifiedPendingTransactions = DB.instance.get<dynamic>(
|
||||
boxName: walletId, key: "notifiedPendingTransactions") as Map? ??
|
||||
{};
|
||||
final notifiedConfirmedTransactions = DB.instance.get<dynamic>(
|
||||
boxName: walletId, key: "notifiedConfirmedTransactions") as Map? ??
|
||||
{};
|
||||
|
||||
notifiedPendingTransactions.remove(txid);
|
||||
notifiedConfirmedTransactions.remove(txid);
|
||||
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: walletId,
|
||||
key: "notifiedConfirmedTransactions",
|
||||
value: notifiedConfirmedTransactions);
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: walletId,
|
||||
key: "notifiedPendingTransactions",
|
||||
value: notifiedPendingTransactions);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -194,6 +194,8 @@ class _SVG {
|
|||
String get exitDesktop => "assets/svg/exit-desktop.svg";
|
||||
String get keys => "assets/svg/keys.svg";
|
||||
String get arrowDown => "assets/svg/arrow-down.svg";
|
||||
String get robotHead => "assets/svg/robot-head.svg";
|
||||
String get whirlPool => "assets/svg/whirlpool.svg";
|
||||
|
||||
String get ellipse1 => "assets/svg/Ellipse-43.svg";
|
||||
String get ellipse2 => "assets/svg/Ellipse-42.svg";
|
||||
|
@ -262,6 +264,7 @@ class _PNG {
|
|||
const _PNG();
|
||||
|
||||
String get stack => "assets/images/stack.png";
|
||||
String get unclaimedPaynym => "assets/images/unclaimed.png";
|
||||
String get splash => "assets/images/splash.png";
|
||||
|
||||
String get monero => "assets/images/monero.png";
|
||||
|
|
32
lib/utilities/bip32_utils.dart
Normal file
|
@ -0,0 +1,32 @@
|
|||
import 'package:bip32/bip32.dart' as bip32;
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:bitcoindart/bitcoindart.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
abstract class Bip32Utils {
|
||||
static bip32.BIP32 getBip32RootSync(String mnemonic, NetworkType network) {
|
||||
final seed = bip39.mnemonicToSeed(mnemonic);
|
||||
final networkType = bip32.NetworkType(
|
||||
wif: network.wif,
|
||||
bip32: bip32.Bip32Type(
|
||||
public: network.bip32.public,
|
||||
private: network.bip32.private,
|
||||
),
|
||||
);
|
||||
|
||||
final root = bip32.BIP32.fromSeed(seed, networkType);
|
||||
return root;
|
||||
}
|
||||
|
||||
static Future<bip32.BIP32> getBip32Root(
|
||||
String mnemonic, NetworkType network) async {
|
||||
final root = await compute(_getBip32RootWrapper, Tuple2(mnemonic, network));
|
||||
return root;
|
||||
}
|
||||
|
||||
/// wrapper for compute()
|
||||
static bip32.BIP32 _getBip32RootWrapper(Tuple2<String, NetworkType> args) {
|
||||
return getBip32RootSync(args.item1, args.item2);
|
||||
}
|
||||
}
|
37
lib/utilities/bip47_utils.dart
Normal file
|
@ -0,0 +1,37 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bip47/src/util.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/output.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||
|
||||
abstract class Bip47Utils {
|
||||
/// looks at tx outputs and returns a blinded payment code if found
|
||||
static Uint8List? getBlindedPaymentCodeBytesFrom(Transaction transaction) {
|
||||
for (int i = 0; i < transaction.outputs.length; i++) {
|
||||
final bytes = getBlindedPaymentCodeBytesFromOutput(
|
||||
transaction.outputs.elementAt(i));
|
||||
if (bytes != null) {
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static Uint8List? getBlindedPaymentCodeBytesFromOutput(Output output) {
|
||||
Uint8List? blindedCodeBytes;
|
||||
|
||||
List<String>? scriptChunks = output.scriptPubKeyAsm?.split(" ");
|
||||
if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") {
|
||||
final blindedPaymentCode = scriptChunks![1];
|
||||
final bytes = blindedPaymentCode.fromHex;
|
||||
|
||||
// https://en.bitcoin.it/wiki/BIP_0047#Sending
|
||||
if (bytes.length == 80 && bytes.first == 1) {
|
||||
blindedCodeBytes = bytes;
|
||||
}
|
||||
}
|
||||
|
||||
return blindedCodeBytes;
|
||||
}
|
||||
}
|
|
@ -4,9 +4,12 @@ import 'package:stackwallet/hive/db.dart';
|
|||
import 'package:stackwallet/utilities/logger.dart';
|
||||
|
||||
const String _kKeyBlobKey = "swbKeyBlobKeyStringID";
|
||||
const String _kKeyBlobVersionKey = "swbKeyBlobVersionKeyStringID";
|
||||
|
||||
const int kLatestBlobVersion = 2;
|
||||
|
||||
String _getMessageFromException(Object exception) {
|
||||
if (exception is IncorrectPassphrase) {
|
||||
if (exception is IncorrectPassphraseOrVersion) {
|
||||
return exception.errMsg();
|
||||
}
|
||||
if (exception is BadDecryption) {
|
||||
|
@ -18,6 +21,9 @@ String _getMessageFromException(Object exception) {
|
|||
if (exception is EncodingError) {
|
||||
return exception.errMsg();
|
||||
}
|
||||
if (exception is VersionError) {
|
||||
return exception.errMsg();
|
||||
}
|
||||
|
||||
return exception.toString();
|
||||
}
|
||||
|
@ -41,7 +47,10 @@ class DPS {
|
|||
}
|
||||
|
||||
try {
|
||||
_handler = await StorageCryptoHandler.fromNewPassphrase(passphrase);
|
||||
_handler = await StorageCryptoHandler.fromNewPassphrase(
|
||||
passphrase,
|
||||
kLatestBlobVersion,
|
||||
);
|
||||
|
||||
final box = await Hive.openBox<String>(DB.boxNameDesktopData);
|
||||
await DB.instance.put<String>(
|
||||
|
@ -49,6 +58,7 @@ class DPS {
|
|||
key: _kKeyBlobKey,
|
||||
value: await _handler!.getKeyBlob(),
|
||||
);
|
||||
await _updateStoredKeyBlobVersion(kLatestBlobVersion);
|
||||
await box.close();
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
|
@ -78,7 +88,24 @@ class DPS {
|
|||
}
|
||||
|
||||
try {
|
||||
_handler = await StorageCryptoHandler.fromExisting(passphrase, keyBlob);
|
||||
final blobVersion = await _getStoredKeyBlobVersion();
|
||||
_handler = await StorageCryptoHandler.fromExisting(
|
||||
passphrase,
|
||||
keyBlob,
|
||||
blobVersion,
|
||||
);
|
||||
if (blobVersion < kLatestBlobVersion) {
|
||||
// update blob
|
||||
await _handler!.resetPassphrase(passphrase, kLatestBlobVersion);
|
||||
final box = await Hive.openBox<String>(DB.boxNameDesktopData);
|
||||
await DB.instance.put<String>(
|
||||
boxName: DB.boxNameDesktopData,
|
||||
key: _kKeyBlobKey,
|
||||
value: await _handler!.getKeyBlob(),
|
||||
);
|
||||
await _updateStoredKeyBlobVersion(kLatestBlobVersion);
|
||||
await box.close();
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"${_getMessageFromException(e)}\n$s",
|
||||
|
@ -102,7 +129,8 @@ class DPS {
|
|||
}
|
||||
|
||||
try {
|
||||
await StorageCryptoHandler.fromExisting(passphrase, keyBlob);
|
||||
final blobVersion = await _getStoredKeyBlobVersion();
|
||||
await StorageCryptoHandler.fromExisting(passphrase, keyBlob, blobVersion);
|
||||
// existing passphrase matches key blob
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
|
@ -135,8 +163,10 @@ class DPS {
|
|||
return false;
|
||||
}
|
||||
|
||||
final blobVersion = await _getStoredKeyBlobVersion();
|
||||
|
||||
try {
|
||||
await _handler!.resetPassphrase(passphraseNew);
|
||||
await _handler!.resetPassphrase(passphraseNew, blobVersion);
|
||||
|
||||
final box = await Hive.openBox<String>(DB.boxNameDesktopData);
|
||||
await DB.instance.put<String>(
|
||||
|
@ -144,6 +174,7 @@ class DPS {
|
|||
key: _kKeyBlobKey,
|
||||
value: await _handler!.getKeyBlob(),
|
||||
);
|
||||
await _updateStoredKeyBlobVersion(blobVersion);
|
||||
await box.close();
|
||||
|
||||
// successfully updated passphrase
|
||||
|
@ -164,4 +195,22 @@ class DPS {
|
|||
);
|
||||
return keyBlob != null;
|
||||
}
|
||||
|
||||
Future<int> _getStoredKeyBlobVersion() async {
|
||||
final box = await Hive.openBox<String>(DB.boxNameDesktopData);
|
||||
final keyBlobVersionString = DB.instance.get<String>(
|
||||
boxName: DB.boxNameDesktopData,
|
||||
key: _kKeyBlobVersionKey,
|
||||
);
|
||||
await box.close();
|
||||
return int.tryParse(keyBlobVersionString ?? "1") ?? 1;
|
||||
}
|
||||
|
||||
Future<void> _updateStoredKeyBlobVersion(int version) async {
|
||||
await DB.instance.put<String>(
|
||||
boxName: DB.boxNameDesktopData,
|
||||
key: _kKeyBlobVersionKey,
|
||||
value: version.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,30 +171,6 @@ extension CoinExt on Coin {
|
|||
}
|
||||
}
|
||||
|
||||
bool get hasPaynymSupport {
|
||||
switch (this) {
|
||||
case Coin.bitcoin:
|
||||
case Coin.litecoin:
|
||||
case Coin.bitcoincash:
|
||||
case Coin.firo:
|
||||
case Coin.namecoin:
|
||||
case Coin.particl:
|
||||
case Coin.bitcoinTestNet:
|
||||
case Coin.litecoinTestNet:
|
||||
case Coin.bitcoincashTestnet:
|
||||
case Coin.firoTestNet:
|
||||
case Coin.epicCash:
|
||||
case Coin.monero:
|
||||
case Coin.wownero:
|
||||
return false;
|
||||
|
||||
case Coin.dogecoin:
|
||||
case Coin.dogecoinTestNet:
|
||||
// return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool get hasBuySupport {
|
||||
switch (this) {
|
||||
case Coin.bitcoin:
|
||||
|
|
36
lib/utilities/enums/derive_path_type_enum.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
||||
enum DerivePathType {
|
||||
bip44,
|
||||
bch44,
|
||||
bip49,
|
||||
bip84,
|
||||
}
|
||||
|
||||
extension DerivePathTypeExt on DerivePathType {
|
||||
static DerivePathType primaryFor(Coin coin) {
|
||||
switch (coin) {
|
||||
case Coin.bitcoincash:
|
||||
case Coin.bitcoincashTestnet:
|
||||
case Coin.dogecoin:
|
||||
case Coin.dogecoinTestNet:
|
||||
case Coin.firo:
|
||||
case Coin.firoTestNet:
|
||||
return DerivePathType.bip44;
|
||||
|
||||
case Coin.bitcoin:
|
||||
case Coin.bitcoinTestNet:
|
||||
case Coin.litecoin:
|
||||
case Coin.litecoinTestNet:
|
||||
case Coin.namecoin:
|
||||
case Coin.particl:
|
||||
return DerivePathType.bip84;
|
||||
|
||||
case Coin.epicCash:
|
||||
case Coin.monero:
|
||||
case Coin.wownero:
|
||||
throw UnsupportedError(
|
||||
"$coin does not use bitcoin style derivation paths");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
abstract class FeaturedPaynyms {
|
||||
// TODO: replace with actual values
|
||||
static const String samouraiWalletDevFund =
|
||||
"PM8TJYkuSdYXJnwDBq8ChfinfXv3srxhQrx3eoEwbSw51wMjdo9JJ2DsycwT3gt3zHQ7cV1grvabMmmf1Btj6fY7tgkgSz9B8MZuR3kjYfgMLMURJCXN";
|
||||
// TODO: replace with actual value
|
||||
// static const String samouraiWalletDevFund =
|
||||
// "PM8TJYkuSdYXJnwDBq8ChfinfXv3srxhQrx3eoEwbSw51wMjdo9JJ2DsycwT3gt3zHQ7cV1grvabMmmf1Btj6fY7tgkgSz9B8MZuR3kjYfgMLMURJCXN";
|
||||
static const String stackWallet =
|
||||
"PM8TJYkuSdYXJnwDBq8ChfinfXv3srxhQrx3eoEwbSw51wMjdo9JJ2DsycwT3gt3zHQ7cV1grvabMmmf1Btj6fY7tgkgSz9B8MZuR3kjYfgMLMURJCXN";
|
||||
"PM8TJdQcNk27JpxGRtNR7Hnh8VkJk4Nf17BthLx89fM3iX3UL2YshyaiTAvKgTCVvpgsAgY1DbojkAaUd3Rcn48NEn4uUBuqkaSddgKL8TPAAEQXNuE6";
|
||||
|
||||
static Map<String, String> get featured => {
|
||||
"Stack Wallet": stackWallet,
|
||||
"Samourai Wallet Dev Fund": samouraiWalletDevFund,
|
||||
// "Samourai Wallet Dev Fund": samouraiWalletDevFund,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -46,6 +46,8 @@ abstract class SecureStorageInterface {
|
|||
MacOsOptions? mOptions,
|
||||
WindowsOptions? wOptions,
|
||||
});
|
||||
|
||||
Future<List<String>> get keys;
|
||||
}
|
||||
|
||||
class DesktopSecureStore {
|
||||
|
@ -110,6 +112,10 @@ class DesktopSecureStore {
|
|||
await isar.encryptedStringValues.deleteByKey(key);
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<String>> get keys async {
|
||||
return await isar.encryptedStringValues.where().keyProperty().findAll();
|
||||
}
|
||||
}
|
||||
|
||||
/// all *Options params ignored on desktop
|
||||
|
@ -229,6 +235,15 @@ class SecureStorageWrapper implements SecureStorageInterface {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> get keys async {
|
||||
if (_isDesktop) {
|
||||
return (_store as DesktopSecureStore).keys;
|
||||
} else {
|
||||
return (await (_store as FlutterSecureStorage).readAll()).keys.toList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock class for testing purposes
|
||||
|
@ -305,4 +320,7 @@ class FakeSecureStorage implements SecureStorageInterface {
|
|||
|
||||
@override
|
||||
dynamic get store => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<List<String>> get keys => Future(() => _store.keys.toList());
|
||||
}
|
||||
|
|
|
@ -13,31 +13,31 @@ class STextStyles {
|
|||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark3,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.oceanBreeze:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark3,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.dark:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark3,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.oledBlack:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark3,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.fruitSorbet:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark3,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
fontSize: 14,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -932,6 +932,76 @@ class STextStyles {
|
|||
}
|
||||
}
|
||||
|
||||
static TextStyle w600_14(BuildContext context) {
|
||||
switch (_theme(context).themeType) {
|
||||
case ThemeType.light:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.oceanBreeze:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.dark:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.oledBlack:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.fruitSorbet:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static TextStyle w500_14(BuildContext context) {
|
||||
switch (_theme(context).themeType) {
|
||||
case ThemeType.light:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.oceanBreeze:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.dark:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.oledBlack:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
);
|
||||
case ThemeType.fruitSorbet:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static TextStyle w500_12(BuildContext context) {
|
||||
switch (_theme(context).themeType) {
|
||||
case ThemeType.light:
|
||||
|
@ -967,6 +1037,41 @@ class STextStyles {
|
|||
}
|
||||
}
|
||||
|
||||
static TextStyle w500_10(BuildContext context) {
|
||||
switch (_theme(context).themeType) {
|
||||
case ThemeType.light:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10,
|
||||
);
|
||||
case ThemeType.oceanBreeze:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10,
|
||||
);
|
||||
case ThemeType.dark:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10,
|
||||
);
|
||||
case ThemeType.oledBlack:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10,
|
||||
);
|
||||
case ThemeType.fruitSorbet:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static TextStyle syncPercent(BuildContext context) {
|
||||
switch (_theme(context).themeType) {
|
||||
case ThemeType.light:
|
||||
|
@ -1030,7 +1135,7 @@ class STextStyles {
|
|||
);
|
||||
case ThemeType.fruitSorbet:
|
||||
return GoogleFonts.inter(
|
||||
color: _theme(context).textDark,
|
||||
color: _theme(context).bottomNavIconIcon,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
);
|
||||
|
|
|
@ -327,7 +327,7 @@ class FruitSorbetColors extends StackColorTheme {
|
|||
@override
|
||||
Color get rateTypeToggleDesktopColorOn => const Color(0xFFFFD8CE);
|
||||
@override
|
||||
Color get rateTypeToggleDesktopColorOff => buttonBackSecondary;
|
||||
Color get rateTypeToggleDesktopColorOff => popupBG;
|
||||
|
||||
@override
|
||||
BoxShadow get standardBoxShadow => BoxShadow(
|
||||
|
|
|
@ -94,7 +94,7 @@ class OceanBreezeColors extends StackColorTheme {
|
|||
@override
|
||||
Color get buttonTextPrimary => const Color(0xFFFFFFFF);
|
||||
@override
|
||||
Color get buttonTextSecondary => const Color(0xFF232323);
|
||||
Color get buttonTextSecondary => accentColorDark;
|
||||
@override
|
||||
Color get buttonTextPrimaryDisabled => const Color(0xFFFFFFFF);
|
||||
@override
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class Util {
|
||||
|
@ -22,6 +23,14 @@ abstract class Util {
|
|||
return Platform.isLinux || Platform.isMacOS || Platform.isWindows;
|
||||
}
|
||||
|
||||
static Future<bool> get isIPad async {
|
||||
final deviceInfo = (await DeviceInfoPlugin().deviceInfo);
|
||||
if (deviceInfo is IosDeviceInfo) {
|
||||
return (deviceInfo).name?.toLowerCase().contains("ipad") == true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static MaterialColor createMaterialColor(Color color) {
|
||||
List<double> strengths = <double>[.05];
|
||||
final swatch = <int, Color>{};
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/providers/ui/color_theme_provider.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/rounded_container.dart';
|
||||
|
||||
class BlueTextButton extends ConsumerStatefulWidget {
|
||||
const BlueTextButton({
|
||||
class _CustomTextButton extends StatefulWidget {
|
||||
const _CustomTextButton({
|
||||
Key? key,
|
||||
required this.text,
|
||||
required this.enabledColor,
|
||||
required this.disabledColor,
|
||||
this.onTap,
|
||||
this.enabled = true,
|
||||
this.textSize,
|
||||
|
@ -21,12 +21,14 @@ class BlueTextButton extends ConsumerStatefulWidget {
|
|||
final VoidCallback? onTap;
|
||||
final bool enabled;
|
||||
final double? textSize;
|
||||
final Color enabledColor;
|
||||
final Color disabledColor;
|
||||
|
||||
@override
|
||||
ConsumerState<BlueTextButton> createState() => _BlueTextButtonState();
|
||||
State<_CustomTextButton> createState() => _CustomTextButtonState();
|
||||
}
|
||||
|
||||
class _BlueTextButtonState extends ConsumerState<BlueTextButton>
|
||||
class _CustomTextButtonState extends State<_CustomTextButton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
AnimationController? controller;
|
||||
Animation<dynamic>? animation;
|
||||
|
@ -37,18 +39,14 @@ class _BlueTextButtonState extends ConsumerState<BlueTextButton>
|
|||
@override
|
||||
void initState() {
|
||||
if (widget.enabled) {
|
||||
color = ref.read(colorThemeProvider.state).state.buttonTextBorderless;
|
||||
color = widget.enabledColor;
|
||||
controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
);
|
||||
animation = ColorTween(
|
||||
begin: ref.read(colorThemeProvider.state).state.buttonTextBorderless,
|
||||
end: ref
|
||||
.read(colorThemeProvider.state)
|
||||
.state
|
||||
.buttonTextBorderless
|
||||
.withOpacity(0.4),
|
||||
begin: widget.enabledColor,
|
||||
end: widget.enabledColor.withOpacity(0.4),
|
||||
).animate(controller!);
|
||||
|
||||
animation!.addListener(() {
|
||||
|
@ -57,7 +55,7 @@ class _BlueTextButtonState extends ConsumerState<BlueTextButton>
|
|||
});
|
||||
});
|
||||
} else {
|
||||
color = ref.read(colorThemeProvider.state).state.textSubtitle1;
|
||||
color = widget.disabledColor;
|
||||
}
|
||||
|
||||
super.initState();
|
||||
|
@ -116,3 +114,32 @@ class _BlueTextButtonState extends ConsumerState<BlueTextButton>
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomTextButton extends StatelessWidget {
|
||||
const CustomTextButton({
|
||||
Key? key,
|
||||
required this.text,
|
||||
this.onTap,
|
||||
this.enabled = true,
|
||||
this.textSize,
|
||||
}) : super(key: key);
|
||||
|
||||
final String text;
|
||||
final VoidCallback? onTap;
|
||||
final bool enabled;
|
||||
final double? textSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _CustomTextButton(
|
||||
key: UniqueKey(),
|
||||
text: text,
|
||||
enabledColor:
|
||||
Theme.of(context).extension<StackColors>()!.buttonTextBorderless,
|
||||
disabledColor: Theme.of(context).extension<StackColors>()!.textSubtitle1,
|
||||
enabled: enabled,
|
||||
textSize: textSize,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ import 'package:stackwallet/notifications/show_flush_bar.dart';
|
|||
import 'package:stackwallet/providers/global/paynym_api_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart';
|
||||
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
|
||||
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/desktop/primary_button.dart';
|
||||
|
@ -58,16 +58,18 @@ class _PaynymFollowToggleButtonState
|
|||
),
|
||||
);
|
||||
|
||||
final wallet = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as DogecoinWallet;
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(widget.walletId);
|
||||
|
||||
// get wallet to access paynym calls
|
||||
final wallet = manager.wallet as PaynymWalletInterface;
|
||||
|
||||
final followedAccount = await ref
|
||||
.read(paynymAPIProvider)
|
||||
.nym(widget.paymentCodeStringToFollow, true);
|
||||
|
||||
final myPCode = await wallet.getPaymentCode();
|
||||
final myPCode =
|
||||
await wallet.getPaymentCode(DerivePathTypeExt.primaryFor(manager.coin));
|
||||
|
||||
PaynymResponse<String> token =
|
||||
await ref.read(paynymAPIProvider).token(myPCode.toString());
|
||||
|
@ -158,16 +160,17 @@ class _PaynymFollowToggleButtonState
|
|||
),
|
||||
);
|
||||
|
||||
final wallet = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(widget.walletId)
|
||||
.wallet as DogecoinWallet;
|
||||
final manager =
|
||||
ref.read(walletsChangeNotifierProvider).getManager(widget.walletId);
|
||||
|
||||
final wallet = manager.wallet as PaynymWalletInterface;
|
||||
|
||||
final followedAccount = await ref
|
||||
.read(paynymAPIProvider)
|
||||
.nym(widget.paymentCodeStringToFollow, true);
|
||||
|
||||
final myPCode = await wallet.getPaymentCode();
|
||||
final myPCode =
|
||||
await wallet.getPaymentCode(DerivePathTypeExt.primaryFor(manager.coin));
|
||||
|
||||
PaynymResponse<String> token =
|
||||
await ref.read(paynymAPIProvider).token(myPCode.toString());
|
||||
|
@ -269,8 +272,8 @@ class _PaynymFollowToggleButtonState
|
|||
switch (widget.style) {
|
||||
case PaynymFollowToggleButtonStyle.primary:
|
||||
return PrimaryButton(
|
||||
width: isDesktop ? 120 : 84,
|
||||
buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.l,
|
||||
width: isDesktop ? 120 : 100,
|
||||
buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.xl,
|
||||
label: isFollowing ? "Unfollow" : "Follow",
|
||||
onPressed: _onPressed,
|
||||
);
|
||||
|
@ -278,15 +281,15 @@ class _PaynymFollowToggleButtonState
|
|||
case PaynymFollowToggleButtonStyle.detailsPopup:
|
||||
return SecondaryButton(
|
||||
label: isFollowing ? "Unfollow" : "Follow",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
buttonHeight: ButtonHeight.xl,
|
||||
iconSpacing: 8,
|
||||
icon: SvgPicture.asset(
|
||||
isFollowing ? Assets.svg.userMinus : Assets.svg.userPlus,
|
||||
width: 10,
|
||||
height: 10,
|
||||
width: 16,
|
||||
height: 16,
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.buttonTextSecondary,
|
||||
),
|
||||
iconSpacing: 4,
|
||||
onPressed: _onPressed,
|
||||
);
|
||||
|
||||
|
|