mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-16 17:27:39 +00:00
Merge pull request #887 from cypherstack/testing
Various fixes and tweaks
This commit is contained in:
commit
cb4a9b5180
37 changed files with 5253 additions and 391 deletions
|
@ -8,7 +8,6 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
|
@ -40,14 +39,18 @@ class DbVersionMigrator with WalletDB {
|
|||
int fromVersion, {
|
||||
required SecureStorageInterface secureStore,
|
||||
}) async {
|
||||
if (AppConfig.appName == "Campfire" && fromVersion < 12) {
|
||||
// safe to skip to v11 for campfire
|
||||
fromVersion = 11;
|
||||
}
|
||||
Logging.instance.log(
|
||||
"Running migrate fromVersion $fromVersion",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
switch (fromVersion) {
|
||||
case 0:
|
||||
await Hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
await Hive.openBox<dynamic>(DB.boxNamePrefs);
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNamePrefs);
|
||||
final walletsService = WalletsService();
|
||||
final nodeService = NodeService(secureStorageInterface: secureStore);
|
||||
final prefs = Prefs.instance;
|
||||
|
@ -61,8 +64,8 @@ class DbVersionMigrator with WalletDB {
|
|||
// only instantiate client if there are firo wallets
|
||||
if (walletInfoList.values
|
||||
.any((element) => element.coinIdentifier == firo.identifier)) {
|
||||
await Hive.openBox<NodeModel>(DB.boxNameNodeModels);
|
||||
await Hive.openBox<NodeModel>(DB.boxNamePrimaryNodes);
|
||||
await DB.instance.hive.openBox<NodeModel>(DB.boxNameNodeModels);
|
||||
await DB.instance.hive.openBox<NodeModel>(DB.boxNamePrimaryNodes);
|
||||
final node =
|
||||
nodeService.getPrimaryNodeFor(currency: firo) ?? firo.defaultNode;
|
||||
final List<ElectrumXNode> failovers = nodeService
|
||||
|
@ -106,7 +109,7 @@ class DbVersionMigrator with WalletDB {
|
|||
for (final walletInfo in walletInfoList.values) {
|
||||
// migrate each firo wallet's lelantus coins
|
||||
if (walletInfo.coinIdentifier == firo.identifier) {
|
||||
await Hive.openBox<dynamic>(walletInfo.walletId);
|
||||
await DB.instance.hive.openBox<dynamic>(walletInfo.walletId);
|
||||
final _lelantusCoins = DB.instance.get<dynamic>(
|
||||
boxName: walletInfo.walletId,
|
||||
key: '_lelantus_coins',
|
||||
|
@ -157,8 +160,8 @@ class DbVersionMigrator with WalletDB {
|
|||
return await migrate(1, secureStore: secureStore);
|
||||
|
||||
case 1:
|
||||
await Hive.openBox<ExchangeTransaction>(DB.boxNameTrades);
|
||||
await Hive.openBox<Trade>(DB.boxNameTradesV2);
|
||||
await DB.instance.hive.openBox<ExchangeTransaction>(DB.boxNameTrades);
|
||||
await DB.instance.hive.openBox<Trade>(DB.boxNameTradesV2);
|
||||
final trades =
|
||||
DB.instance.values<ExchangeTransaction>(boxName: DB.boxNameTrades);
|
||||
|
||||
|
@ -184,7 +187,7 @@ class DbVersionMigrator with WalletDB {
|
|||
return await migrate(2, secureStore: secureStore);
|
||||
|
||||
case 2:
|
||||
await Hive.openBox<dynamic>(DB.boxNamePrefs);
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNamePrefs);
|
||||
final prefs = Prefs.instance;
|
||||
await prefs.init();
|
||||
if (!(await prefs.isExternalCallsSet())) {
|
||||
|
@ -233,8 +236,8 @@ class DbVersionMigrator with WalletDB {
|
|||
|
||||
case 5:
|
||||
// migrate
|
||||
await Hive.openBox<dynamic>("theme");
|
||||
await Hive.openBox<dynamic>(DB.boxNamePrefs);
|
||||
await DB.instance.hive.openBox<dynamic>("theme");
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNamePrefs);
|
||||
|
||||
final themeName =
|
||||
DB.instance.get<dynamic>(boxName: "theme", key: "colorScheme")
|
||||
|
@ -347,7 +350,7 @@ class DbVersionMigrator with WalletDB {
|
|||
|
||||
case 8:
|
||||
// migrate
|
||||
await Hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
final walletsService = WalletsService();
|
||||
final walletInfoList = await walletsService.walletNames;
|
||||
await MainDB.instance.initMainDB();
|
||||
|
@ -443,8 +446,8 @@ class DbVersionMigrator with WalletDB {
|
|||
}
|
||||
|
||||
Future<void> _v4(SecureStorageInterface secureStore) async {
|
||||
await Hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
await Hive.openBox<dynamic>(DB.boxNamePrefs);
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNamePrefs);
|
||||
final walletsService = WalletsService();
|
||||
final prefs = Prefs.instance;
|
||||
final walletInfoList = await walletsService.walletNames;
|
||||
|
@ -455,7 +458,7 @@ class DbVersionMigrator with WalletDB {
|
|||
final info = walletInfoList[walletId]!;
|
||||
assert(info.walletId == walletId);
|
||||
|
||||
final walletBox = await Hive.openBox<dynamic>(info.walletId);
|
||||
final walletBox = await DB.instance.hive.openBox<dynamic>(info.walletId);
|
||||
|
||||
const receiveAddressesPrefix = "receivingAddresses";
|
||||
const changeAddressesPrefix = "changeAddresses";
|
||||
|
@ -560,7 +563,7 @@ class DbVersionMigrator with WalletDB {
|
|||
}
|
||||
|
||||
Future<void> _v7(SecureStorageInterface secureStore) async {
|
||||
await Hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
final walletsService = WalletsService();
|
||||
final walletInfoList = await walletsService.walletNames;
|
||||
await MainDB.instance.initMainDB();
|
||||
|
@ -601,7 +604,8 @@ class DbVersionMigrator with WalletDB {
|
|||
}
|
||||
|
||||
Future<void> _v9() async {
|
||||
final addressBookBox = await Hive.openBox<dynamic>(DB.boxNameAddressBook);
|
||||
final addressBookBox =
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNameAddressBook);
|
||||
await MainDB.instance.initMainDB();
|
||||
|
||||
final keys = List<String>.from(addressBookBox.keys);
|
||||
|
@ -649,8 +653,8 @@ class DbVersionMigrator with WalletDB {
|
|||
}
|
||||
|
||||
Future<void> _v10(SecureStorageInterface secureStore) async {
|
||||
await Hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
await Hive.openBox<dynamic>(DB.boxNamePrefs);
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNamePrefs);
|
||||
final walletsService = WalletsService();
|
||||
final prefs = Prefs.instance;
|
||||
final walletInfoList = await walletsService.walletNames;
|
||||
|
@ -669,7 +673,7 @@ class DbVersionMigrator with WalletDB {
|
|||
.walletIdEqualTo(walletId)
|
||||
.countSync() ==
|
||||
0) {
|
||||
final walletBox = await Hive.openBox<dynamic>(walletId);
|
||||
final walletBox = await DB.instance.hive.openBox<dynamic>(walletId);
|
||||
|
||||
final hiveLCoins = DB.instance.get<dynamic>(
|
||||
boxName: walletId,
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
import 'dart:isolate';
|
||||
|
||||
import 'package:cw_core/wallet_info.dart' as xmr;
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:hive/hive.dart' show Box;
|
||||
import 'package:hive/src/hive_impl.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
|
||||
import '../../app_config.dart';
|
||||
|
@ -24,6 +25,8 @@ import '../../utilities/logger.dart';
|
|||
import '../../wallets/crypto_currency/crypto_currency.dart';
|
||||
|
||||
class DB {
|
||||
final hive = HiveImpl();
|
||||
|
||||
// legacy (required for migrations)
|
||||
@Deprecated("Left over for migration from old versions of Stack Wallet")
|
||||
static const String boxNameAddressBook = "addressBook";
|
||||
|
@ -104,52 +107,52 @@ class DB {
|
|||
|
||||
// open hive boxes
|
||||
Future<void> init() async {
|
||||
if (Hive.isBoxOpen(boxNameDBInfo)) {
|
||||
_boxDBInfo = Hive.box<dynamic>(boxNameDBInfo);
|
||||
if (hive.isBoxOpen(boxNameDBInfo)) {
|
||||
_boxDBInfo = hive.box<dynamic>(boxNameDBInfo);
|
||||
} else {
|
||||
_boxDBInfo = await Hive.openBox<dynamic>(boxNameDBInfo);
|
||||
_boxDBInfo = await hive.openBox<dynamic>(boxNameDBInfo);
|
||||
}
|
||||
await Hive.openBox<String>(boxNameWalletsToDeleteOnStart);
|
||||
await hive.openBox<String>(boxNameWalletsToDeleteOnStart);
|
||||
|
||||
if (Hive.isBoxOpen(boxNamePrefs)) {
|
||||
_boxPrefs = Hive.box<dynamic>(boxNamePrefs);
|
||||
if (hive.isBoxOpen(boxNamePrefs)) {
|
||||
_boxPrefs = hive.box<dynamic>(boxNamePrefs);
|
||||
} else {
|
||||
_boxPrefs = await Hive.openBox<dynamic>(boxNamePrefs);
|
||||
_boxPrefs = await hive.openBox<dynamic>(boxNamePrefs);
|
||||
}
|
||||
|
||||
if (Hive.isBoxOpen(boxNameNodeModels)) {
|
||||
_boxNodeModels = Hive.box<NodeModel>(boxNameNodeModels);
|
||||
if (hive.isBoxOpen(boxNameNodeModels)) {
|
||||
_boxNodeModels = hive.box<NodeModel>(boxNameNodeModels);
|
||||
} else {
|
||||
_boxNodeModels = await Hive.openBox<NodeModel>(boxNameNodeModels);
|
||||
_boxNodeModels = await hive.openBox<NodeModel>(boxNameNodeModels);
|
||||
}
|
||||
|
||||
if (Hive.isBoxOpen(boxNamePrimaryNodes)) {
|
||||
_boxPrimaryNodes = Hive.box<NodeModel>(boxNamePrimaryNodes);
|
||||
if (hive.isBoxOpen(boxNamePrimaryNodes)) {
|
||||
_boxPrimaryNodes = hive.box<NodeModel>(boxNamePrimaryNodes);
|
||||
} else {
|
||||
_boxPrimaryNodes = await Hive.openBox<NodeModel>(boxNamePrimaryNodes);
|
||||
_boxPrimaryNodes = await hive.openBox<NodeModel>(boxNamePrimaryNodes);
|
||||
}
|
||||
|
||||
if (Hive.isBoxOpen(boxNameAllWalletsData)) {
|
||||
_boxAllWalletsData = Hive.box<dynamic>(boxNameAllWalletsData);
|
||||
if (hive.isBoxOpen(boxNameAllWalletsData)) {
|
||||
_boxAllWalletsData = hive.box<dynamic>(boxNameAllWalletsData);
|
||||
} else {
|
||||
_boxAllWalletsData = await Hive.openBox<dynamic>(boxNameAllWalletsData);
|
||||
_boxAllWalletsData = await hive.openBox<dynamic>(boxNameAllWalletsData);
|
||||
}
|
||||
|
||||
_boxNotifications =
|
||||
await Hive.openBox<NotificationModel>(boxNameNotifications);
|
||||
await hive.openBox<NotificationModel>(boxNameNotifications);
|
||||
_boxWatchedTransactions =
|
||||
await Hive.openBox<NotificationModel>(boxNameWatchedTransactions);
|
||||
await hive.openBox<NotificationModel>(boxNameWatchedTransactions);
|
||||
_boxWatchedTrades =
|
||||
await Hive.openBox<NotificationModel>(boxNameWatchedTrades);
|
||||
_boxTradesV2 = await Hive.openBox<Trade>(boxNameTradesV2);
|
||||
_boxTradeNotes = await Hive.openBox<String>(boxNameTradeNotes);
|
||||
_boxTradeLookup = await Hive.openBox<TradeWalletLookup>(boxNameTradeLookup);
|
||||
await hive.openBox<NotificationModel>(boxNameWatchedTrades);
|
||||
_boxTradesV2 = await hive.openBox<Trade>(boxNameTradesV2);
|
||||
_boxTradeNotes = await hive.openBox<String>(boxNameTradeNotes);
|
||||
_boxTradeLookup = await hive.openBox<TradeWalletLookup>(boxNameTradeLookup);
|
||||
_walletInfoSource =
|
||||
await Hive.openBox<xmr.WalletInfo>(xmr.WalletInfo.boxName);
|
||||
_boxFavoriteWallets = await Hive.openBox<String>(boxNameFavoriteWallets);
|
||||
await hive.openBox<xmr.WalletInfo>(xmr.WalletInfo.boxName);
|
||||
_boxFavoriteWallets = await hive.openBox<String>(boxNameFavoriteWallets);
|
||||
|
||||
await Future.wait([
|
||||
Hive.openBox<dynamic>(boxNamePriceCache),
|
||||
hive.openBox<dynamic>(boxNamePriceCache),
|
||||
_loadWalletBoxes(),
|
||||
]);
|
||||
}
|
||||
|
@ -177,12 +180,12 @@ class DB {
|
|||
);
|
||||
|
||||
for (final entry in mapped.entries) {
|
||||
if (Hive.isBoxOpen(entry.value.walletId)) {
|
||||
if (hive.isBoxOpen(entry.value.walletId)) {
|
||||
_walletBoxes[entry.value.walletId] =
|
||||
Hive.box<dynamic>(entry.value.walletId);
|
||||
hive.box<dynamic>(entry.value.walletId);
|
||||
} else {
|
||||
_walletBoxes[entry.value.walletId] =
|
||||
await Hive.openBox<dynamic>(entry.value.walletId);
|
||||
await hive.openBox<dynamic>(entry.value.walletId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -192,7 +195,7 @@ class DB {
|
|||
_txCacheBoxes.remove(currency.identifier);
|
||||
}
|
||||
return _txCacheBoxes[currency.identifier] ??=
|
||||
await Hive.openBox<dynamic>(_boxNameTxCache(currency: currency));
|
||||
await hive.openBox<dynamic>(_boxNameTxCache(currency: currency));
|
||||
}
|
||||
|
||||
Future<void> closeTxCacheBox({required CryptoCurrency currency}) async {
|
||||
|
@ -206,7 +209,7 @@ class DB {
|
|||
_setCacheBoxes.remove(currency.identifier);
|
||||
}
|
||||
return _setCacheBoxes[currency.identifier] ??=
|
||||
await Hive.openBox<dynamic>(_boxNameSetCache(currency: currency));
|
||||
await hive.openBox<dynamic>(_boxNameSetCache(currency: currency));
|
||||
}
|
||||
|
||||
Future<void> closeAnonymitySetCacheBox({
|
||||
|
@ -222,7 +225,7 @@ class DB {
|
|||
_usedSerialsCacheBoxes.remove(currency.identifier);
|
||||
}
|
||||
return _usedSerialsCacheBoxes[currency.identifier] ??=
|
||||
await Hive.openBox<dynamic>(
|
||||
await hive.openBox<dynamic>(
|
||||
_boxNameUsedSerialsCache(currency: currency),
|
||||
);
|
||||
}
|
||||
|
@ -252,7 +255,7 @@ class DB {
|
|||
if (_walletBoxes[walletId] != null) {
|
||||
throw Exception("Attempted overwrite of existing wallet box!");
|
||||
}
|
||||
_walletBoxes[walletId] = await Hive.openBox<dynamic>(walletId);
|
||||
_walletBoxes[walletId] = await hive.openBox<dynamic>(walletId);
|
||||
}
|
||||
|
||||
Future<void> removeWalletBox({required String walletId}) async {
|
||||
|
@ -264,19 +267,19 @@ class DB {
|
|||
// reads
|
||||
|
||||
List<dynamic> keys<T>({required String boxName}) =>
|
||||
Hive.box<T>(boxName).keys.toList(growable: false);
|
||||
hive.box<T>(boxName).keys.toList(growable: false);
|
||||
|
||||
List<T> values<T>({required String boxName}) =>
|
||||
Hive.box<T>(boxName).values.toList(growable: false);
|
||||
hive.box<T>(boxName).values.toList(growable: false);
|
||||
|
||||
T? get<T>({
|
||||
required String boxName,
|
||||
required dynamic key,
|
||||
}) =>
|
||||
Hive.box<T>(boxName).get(key);
|
||||
hive.box<T>(boxName).get(key);
|
||||
|
||||
bool containsKey<T>({required String boxName, required dynamic key}) =>
|
||||
Hive.box<T>(boxName).containsKey(key);
|
||||
hive.box<T>(boxName).containsKey(key);
|
||||
|
||||
// writes
|
||||
|
||||
|
@ -286,33 +289,33 @@ class DB {
|
|||
required T value,
|
||||
}) async =>
|
||||
await mutex
|
||||
.protect(() async => await Hive.box<T>(boxName).put(key, value));
|
||||
.protect(() async => await hive.box<T>(boxName).put(key, value));
|
||||
|
||||
Future<void> add<T>({required String boxName, required T value}) async =>
|
||||
await mutex.protect(() async => await Hive.box<T>(boxName).add(value));
|
||||
await mutex.protect(() async => await hive.box<T>(boxName).add(value));
|
||||
|
||||
Future<void> addAll<T>({
|
||||
required String boxName,
|
||||
required Iterable<T> values,
|
||||
}) async =>
|
||||
await mutex
|
||||
.protect(() async => await Hive.box<T>(boxName).addAll(values));
|
||||
.protect(() async => await hive.box<T>(boxName).addAll(values));
|
||||
|
||||
Future<void> delete<T>({
|
||||
required dynamic key,
|
||||
required String boxName,
|
||||
}) async =>
|
||||
await mutex.protect(() async => await Hive.box<T>(boxName).delete(key));
|
||||
await mutex.protect(() async => await hive.box<T>(boxName).delete(key));
|
||||
|
||||
Future<void> deleteAll<T>({required String boxName}) async {
|
||||
await mutex.protect(() async {
|
||||
final box = await Hive.openBox<T>(boxName);
|
||||
final box = await hive.openBox<T>(boxName);
|
||||
await box.clear();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> deleteBoxFromDisk({required String boxName}) async =>
|
||||
await mutex.protect(() async => await Hive.deleteBoxFromDisk(boxName));
|
||||
await mutex.protect(() async => await hive.deleteBoxFromDisk(boxName));
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
Future<bool> deleteEverything() async {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
import '../app_config.dart';
|
||||
|
@ -24,7 +23,8 @@ Future<void> migrateWalletsToIsar({
|
|||
await MainDB.instance.isar
|
||||
.writeTxn(() async => await MainDB.instance.isar.transactionV2s.clear());
|
||||
|
||||
final allWalletsBox = await Hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
final allWalletsBox =
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
|
||||
final names = DB.instance
|
||||
.get<dynamic>(boxName: DB.boxNameAllWalletsData, key: 'names') as Map?;
|
||||
|
@ -55,7 +55,9 @@ Future<void> migrateWalletsToIsar({
|
|||
// Get current ordered list of favourite wallet Ids
|
||||
//
|
||||
final List<String> favourites =
|
||||
(await Hive.openBox<String>(DB.boxNameFavoriteWallets)).values.toList();
|
||||
(await DB.instance.hive.openBox<String>(DB.boxNameFavoriteWallets))
|
||||
.values
|
||||
.toList();
|
||||
|
||||
final List<(WalletInfo, WalletInfoMeta)> newInfo = [];
|
||||
final List<TokenWalletInfo> tokenInfo = [];
|
||||
|
@ -65,7 +67,7 @@ Future<void> migrateWalletsToIsar({
|
|||
// Convert each old info into the new Isar WalletInfo
|
||||
//
|
||||
for (final old in oldInfo) {
|
||||
final walletBox = await Hive.openBox<dynamic>(old.walletId);
|
||||
final walletBox = await DB.instance.hive.openBox<dynamic>(old.walletId);
|
||||
|
||||
//
|
||||
// First handle transaction notes
|
||||
|
@ -212,9 +214,9 @@ Future<void> migrateWalletsToIsar({
|
|||
}
|
||||
|
||||
Future<void> _cleanupOnSuccess({required List<String> walletIds}) async {
|
||||
await Hive.deleteBoxFromDisk(DB.boxNameFavoriteWallets);
|
||||
await Hive.deleteBoxFromDisk(DB.boxNameAllWalletsData);
|
||||
await DB.instance.hive.deleteBoxFromDisk(DB.boxNameFavoriteWallets);
|
||||
await DB.instance.hive.deleteBoxFromDisk(DB.boxNameAllWalletsData);
|
||||
for (final walletId in walletIds) {
|
||||
await Hive.deleteBoxFromDisk(walletId);
|
||||
await DB.instance.hive.deleteBoxFromDisk(walletId);
|
||||
}
|
||||
}
|
||||
|
|
79
lib/db/special_migrations.dart
Normal file
79
lib/db/special_migrations.dart
Normal file
|
@ -0,0 +1,79 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:hive/hive.dart' show Box;
|
||||
import 'package:hive/src/hive_impl.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../app_config.dart';
|
||||
import '../utilities/util.dart';
|
||||
import 'hive/db.dart';
|
||||
|
||||
abstract class CampfireMigration {
|
||||
static const _didRunKey = "campfire_one_time_migration_done_key";
|
||||
|
||||
static bool get didRun =>
|
||||
DB.instance.get<dynamic>(
|
||||
boxName: DB.boxNameDBInfo,
|
||||
key: _didRunKey,
|
||||
) as bool? ??
|
||||
false;
|
||||
|
||||
static Future<void> setDidRun() async {
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: DB.boxNameDBInfo,
|
||||
key: _didRunKey,
|
||||
value: true,
|
||||
);
|
||||
}
|
||||
|
||||
static bool get hasOldWallets =>
|
||||
!didRun && (_wallets?.get("names") as Map?)?.isNotEmpty == true;
|
||||
|
||||
static late final FlutterSecureStorage? _secureStore;
|
||||
static late final Box<dynamic>? _wallets;
|
||||
|
||||
static Future<void> init() async {
|
||||
if (didRun || Util.isDesktop) {
|
||||
return;
|
||||
}
|
||||
final Directory appDirectory = await getApplicationDocumentsDirectory();
|
||||
|
||||
final file = File("${appDirectory.path}/wallets.hive");
|
||||
|
||||
if (await file.exists()) {
|
||||
final myHive = HiveImpl();
|
||||
myHive.init(appDirectory.path);
|
||||
_wallets = await myHive.openBox<dynamic>('wallets');
|
||||
_secureStore = const FlutterSecureStorage();
|
||||
} else {
|
||||
await setDidRun();
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<(String, List<String>)>> fetch() async {
|
||||
if (didRun ||
|
||||
Util.isDesktop ||
|
||||
AppConfig.appName != "Campfire" ||
|
||||
_wallets == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final names = _wallets!.get("names");
|
||||
|
||||
final List<(String, List<String>)> results = [];
|
||||
if (names is Map) {
|
||||
for (final entry in names.entries) {
|
||||
final name = entry.key as String;
|
||||
final id = entry.value as String;
|
||||
final mnemonic = await _secureStore!.read(key: "${id}_mnemonic");
|
||||
|
||||
if (mnemonic != null) {
|
||||
results.add((name, mnemonic.split(" ")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
|
@ -30,7 +30,7 @@ void _debugLog(Object? object) {
|
|||
|
||||
abstract class _FiroCache {
|
||||
static const int _setCacheVersion = 1;
|
||||
static const int _tagsCacheVersion = 1;
|
||||
static const int _tagsCacheVersion = 2;
|
||||
static const String sparkSetCacheFileName =
|
||||
"spark_set_v$_setCacheVersion.sqlite3";
|
||||
static const String sparkUsedTagsCacheFileName =
|
||||
|
@ -154,7 +154,8 @@ abstract class _FiroCache {
|
|||
"""
|
||||
CREATE TABLE SparkUsedCoinTags (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
tag TEXT NOT NULL UNIQUE
|
||||
tag TEXT NOT NULL UNIQUE,
|
||||
txid TEXT NOT NULL
|
||||
);
|
||||
""",
|
||||
);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
part of 'firo_cache.dart';
|
||||
|
||||
typedef LTagPair = ({String tag, String txid});
|
||||
|
||||
/// Wrapper class for [_FiroCache] as [_FiroCache] should eventually be handled in a
|
||||
/// background isolate and [FiroCacheCoordinator] should manage that isolate
|
||||
abstract class FiroCacheCoordinator {
|
||||
|
@ -51,7 +53,7 @@ abstract class FiroCacheCoordinator {
|
|||
ElectrumXClient client,
|
||||
) async {
|
||||
final count = await FiroCacheCoordinator.getUsedCoinTagsCount();
|
||||
final unhashedTags = await client.getSparkUnhashedUsedCoinsTags(
|
||||
final unhashedTags = await client.getSparkUnhashedUsedCoinsTagsWithTxHashes(
|
||||
startNumber: count,
|
||||
);
|
||||
if (unhashedTags.isNotEmpty) {
|
||||
|
@ -97,10 +99,6 @@ abstract class FiroCacheCoordinator {
|
|||
return result.map((e) => e["tag"] as String).toSet();
|
||||
}
|
||||
|
||||
/// This should be the equivalent of counting the number of tags in the db.
|
||||
/// Assuming the integrity of the data. Faster than actually calling count on
|
||||
/// a table where no records have been deleted. None should be deleted from
|
||||
/// this table in practice.
|
||||
static Future<int> getUsedCoinTagsCount() async {
|
||||
final result = await _Reader._getUsedCoinTagsCount(
|
||||
db: _FiroCache.usedTagsCacheDB,
|
||||
|
@ -111,6 +109,40 @@ abstract class FiroCacheCoordinator {
|
|||
return result.first["count"] as int? ?? 0;
|
||||
}
|
||||
|
||||
static Future<List<LTagPair>> getUsedCoinTxidsFor({
|
||||
required List<String> tags,
|
||||
}) async {
|
||||
if (tags.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
final result = await _Reader._getUsedCoinTxidsFor(
|
||||
tags,
|
||||
db: _FiroCache.usedTagsCacheDB,
|
||||
);
|
||||
|
||||
if (result.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
return result.rows
|
||||
.map(
|
||||
(e) => (
|
||||
tag: e[0] as String,
|
||||
txid: e[1] as String,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
static Future<Set<String>> getUsedCoinTagsFor({
|
||||
required String txid,
|
||||
}) async {
|
||||
final result = await _Reader._getUsedCoinTagsFor(
|
||||
txid,
|
||||
db: _FiroCache.usedTagsCacheDB,
|
||||
);
|
||||
return result.map((e) => e["tag"] as String).toSet();
|
||||
}
|
||||
|
||||
static Future<bool> checkTagIsUsed(
|
||||
String tag,
|
||||
) async {
|
||||
|
|
|
@ -86,6 +86,35 @@ abstract class _Reader {
|
|||
return db.select("$query;");
|
||||
}
|
||||
|
||||
static Future<ResultSet> _getUsedCoinTxidsFor(
|
||||
List<String> tags, {
|
||||
required Database db,
|
||||
}) async {
|
||||
final tagsConcat = tags.join("', '");
|
||||
|
||||
final query = """
|
||||
SELECT tag, GROUP_CONCAT(txid) AS txids
|
||||
FROM SparkUsedCoinTags
|
||||
WHERE tag IN ('$tagsConcat')
|
||||
GROUP BY tag;
|
||||
""";
|
||||
|
||||
return db.select("$query;");
|
||||
}
|
||||
|
||||
static Future<ResultSet> _getUsedCoinTagsFor(
|
||||
String txid, {
|
||||
required Database db,
|
||||
}) async {
|
||||
final query = """
|
||||
SELECT tag
|
||||
FROM SparkUsedCoinTags
|
||||
WHERE txid = '$txid';
|
||||
""";
|
||||
|
||||
return db.select("$query;");
|
||||
}
|
||||
|
||||
static Future<bool> _checkTagIsUsed(
|
||||
String tag, {
|
||||
required Database db,
|
||||
|
|
|
@ -100,7 +100,7 @@ class _FiroCacheWorker {
|
|||
case FCFuncName._updateSparkUsedTagsWith:
|
||||
result = _updateSparkUsedTagsWith(
|
||||
usedTagsCacheDb,
|
||||
task.data as List<String>,
|
||||
task.data as List<List<dynamic>>,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -15,10 +15,12 @@ class FCResult {
|
|||
/// returns true if successful, otherwise some exception
|
||||
FCResult _updateSparkUsedTagsWith(
|
||||
Database db,
|
||||
List<String> tags,
|
||||
List<List<dynamic>> tags,
|
||||
) {
|
||||
// hash the tags here since this function is called in a background isolate
|
||||
final hashedTags = LibSpark.hashTags(base64Tags: tags);
|
||||
final hashedTags = LibSpark.hashTags(
|
||||
base64Tags: tags.map((e) => e[0] as String),
|
||||
);
|
||||
|
||||
if (hashedTags.isEmpty) {
|
||||
// nothing to add, return early
|
||||
|
@ -27,13 +29,13 @@ FCResult _updateSparkUsedTagsWith(
|
|||
|
||||
db.execute("BEGIN;");
|
||||
try {
|
||||
for (final tag in hashedTags) {
|
||||
for (int i = 0; i < hashedTags.length; i++) {
|
||||
db.execute(
|
||||
"""
|
||||
INSERT OR IGNORE INTO SparkUsedCoinTags (tag)
|
||||
VALUES (?);
|
||||
INSERT OR IGNORE INTO SparkUsedCoinTags (tag, txid)
|
||||
VALUES (?, ?);
|
||||
""",
|
||||
[tag],
|
||||
[hashedTags[i], (tags[i][1] as String).toHexReversedFromBase64],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -932,41 +932,41 @@ class ElectrumXClient {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: update when we get new call to include tx hashes in response
|
||||
/// NOT USED. See [getSparkUnhashedUsedCoinsTagsWithTxHashes]
|
||||
/// Takes [startNumber], if it is 0, we get the full set,
|
||||
/// otherwise the used tags after that number
|
||||
Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int startNumber,
|
||||
}) async {
|
||||
try {
|
||||
final start = DateTime.now();
|
||||
await _checkElectrumAdapter();
|
||||
final Map<String, dynamic> response =
|
||||
await (getElectrumAdapter() as FiroElectrumClient)
|
||||
.getUsedCoinsTags(startNumber: startNumber);
|
||||
// TODO: Add 2 minute timeout.
|
||||
// Why 2 minutes?
|
||||
Logging.instance.log(
|
||||
"Fetching spark.getusedcoinstags finished",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
final map = Map<String, dynamic>.from(response);
|
||||
final tags = List<String>.from(map["tags"] as List);
|
||||
|
||||
Logging.instance.log(
|
||||
"Finished ElectrumXClient.getSparkUnhashedUsedCoinsTags(startNumber"
|
||||
"=$startNumber). # of tags fetched=${tags.length}, "
|
||||
"Duration=${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
return tags;
|
||||
} catch (e) {
|
||||
Logging.instance.log(e, level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
// Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
// String? requestID,
|
||||
// required int startNumber,
|
||||
// }) async {
|
||||
// try {
|
||||
// final start = DateTime.now();
|
||||
// await _checkElectrumAdapter();
|
||||
// final Map<String, dynamic> response =
|
||||
// await (getElectrumAdapter() as FiroElectrumClient)
|
||||
// .getUsedCoinsTags(startNumber: startNumber);
|
||||
// // TODO: Add 2 minute timeout.
|
||||
// // Why 2 minutes?
|
||||
// Logging.instance.log(
|
||||
// "Fetching spark.getusedcoinstags finished",
|
||||
// level: LogLevel.Info,
|
||||
// );
|
||||
// final map = Map<String, dynamic>.from(response);
|
||||
// final tags = List<String>.from(map["tags"] as List);
|
||||
//
|
||||
// Logging.instance.log(
|
||||
// "Finished ElectrumXClient.getSparkUnhashedUsedCoinsTags(startNumber"
|
||||
// "=$startNumber). # of tags fetched=${tags.length}, "
|
||||
// "Duration=${DateTime.now().difference(start)}",
|
||||
// level: LogLevel.Info,
|
||||
// );
|
||||
//
|
||||
// return tags;
|
||||
// } catch (e) {
|
||||
// Logging.instance.log(e, level: LogLevel.Error);
|
||||
// rethrow;
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Takes a list of [sparkCoinHashes] and returns the set id and block height
|
||||
/// for each coin
|
||||
|
@ -1085,6 +1085,38 @@ class ElectrumXClient {
|
|||
}
|
||||
}
|
||||
|
||||
/// Takes [startNumber], if it is 0, we get the full set,
|
||||
/// otherwise the used tags and txids after that number
|
||||
Future<List<List<dynamic>>> getSparkUnhashedUsedCoinsTagsWithTxHashes({
|
||||
String? requestID,
|
||||
required int startNumber,
|
||||
}) async {
|
||||
try {
|
||||
final start = DateTime.now();
|
||||
final response = await request(
|
||||
requestID: requestID,
|
||||
command: "spark.getusedcoinstagstxhashes",
|
||||
args: [
|
||||
"$startNumber",
|
||||
],
|
||||
);
|
||||
|
||||
final map = Map<String, dynamic>.from(response as Map);
|
||||
final tags = List<List<dynamic>>.from(map["tagsandtxids"] as List);
|
||||
|
||||
Logging.instance.log(
|
||||
"Finished ElectrumXClient.getSparkUnhashedUsedCoinsTagsWithTxHashes("
|
||||
"startNumber=$startNumber). # of tags fetched=${tags.length}, "
|
||||
"Duration=${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
return tags;
|
||||
} catch (e) {
|
||||
Logging.instance.log(e, level: LogLevel.Error);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
// ===========================================================================
|
||||
|
||||
/// Get the current fee rate.
|
||||
|
|
|
@ -25,7 +25,6 @@ import 'package:flutter_libmonero/wownero/wownero.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:keyboard_dismisser/keyboard_dismisser.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
@ -35,6 +34,7 @@ import 'app_config.dart';
|
|||
import 'db/db_version_migration.dart';
|
||||
import 'db/hive/db.dart';
|
||||
import 'db/isar/main_db.dart';
|
||||
import 'db/special_migrations.dart';
|
||||
import 'db/sqlite/firo_cache.dart';
|
||||
import 'models/exchange/change_now/exchange_transaction.dart';
|
||||
import 'models/exchange/change_now/exchange_transaction_status.dart';
|
||||
|
@ -44,6 +44,7 @@ import 'models/models.dart';
|
|||
import 'models/node_model.dart';
|
||||
import 'models/notification_model.dart';
|
||||
import 'models/trade_wallet_lookup.dart';
|
||||
import 'pages/campfire_migrate_view.dart';
|
||||
import 'pages/home_view/home_view.dart';
|
||||
import 'pages/intro_view.dart';
|
||||
import 'pages/loading_view.dart';
|
||||
|
@ -142,52 +143,59 @@ void main(List<String> args) async {
|
|||
}
|
||||
|
||||
// Registering Transaction Model Adapters
|
||||
Hive.registerAdapter(TransactionDataAdapter());
|
||||
Hive.registerAdapter(TransactionChunkAdapter());
|
||||
Hive.registerAdapter(TransactionAdapter());
|
||||
Hive.registerAdapter(InputAdapter());
|
||||
Hive.registerAdapter(OutputAdapter());
|
||||
DB.instance.hive.registerAdapter(TransactionDataAdapter());
|
||||
DB.instance.hive.registerAdapter(TransactionChunkAdapter());
|
||||
DB.instance.hive.registerAdapter(TransactionAdapter());
|
||||
DB.instance.hive.registerAdapter(InputAdapter());
|
||||
DB.instance.hive.registerAdapter(OutputAdapter());
|
||||
|
||||
// Registering Utxo Model Adapters
|
||||
Hive.registerAdapter(UtxoDataAdapter());
|
||||
Hive.registerAdapter(UtxoObjectAdapter());
|
||||
Hive.registerAdapter(StatusAdapter());
|
||||
DB.instance.hive.registerAdapter(UtxoDataAdapter());
|
||||
DB.instance.hive.registerAdapter(UtxoObjectAdapter());
|
||||
DB.instance.hive.registerAdapter(StatusAdapter());
|
||||
|
||||
// Registering Lelantus Model Adapters
|
||||
Hive.registerAdapter(LelantusCoinAdapter());
|
||||
DB.instance.hive.registerAdapter(LelantusCoinAdapter());
|
||||
|
||||
// notification model adapter
|
||||
Hive.registerAdapter(NotificationModelAdapter());
|
||||
DB.instance.hive.registerAdapter(NotificationModelAdapter());
|
||||
|
||||
// change now trade adapters
|
||||
Hive.registerAdapter(ExchangeTransactionAdapter());
|
||||
Hive.registerAdapter(ExchangeTransactionStatusAdapter());
|
||||
DB.instance.hive.registerAdapter(ExchangeTransactionAdapter());
|
||||
DB.instance.hive.registerAdapter(ExchangeTransactionStatusAdapter());
|
||||
|
||||
Hive.registerAdapter(TradeAdapter());
|
||||
DB.instance.hive.registerAdapter(TradeAdapter());
|
||||
|
||||
// reference lookup data adapter
|
||||
Hive.registerAdapter(TradeWalletLookupAdapter());
|
||||
DB.instance.hive.registerAdapter(TradeWalletLookupAdapter());
|
||||
|
||||
// node model adapter
|
||||
Hive.registerAdapter(NodeModelAdapter());
|
||||
DB.instance.hive.registerAdapter(NodeModelAdapter());
|
||||
|
||||
Hive.registerAdapter(NodeAdapter());
|
||||
DB.instance.hive.registerAdapter(NodeAdapter());
|
||||
|
||||
if (!Hive.isAdapterRegistered(WalletInfoAdapter().typeId)) {
|
||||
Hive.registerAdapter(WalletInfoAdapter());
|
||||
if (!DB.instance.hive.isAdapterRegistered(WalletInfoAdapter().typeId)) {
|
||||
DB.instance.hive.registerAdapter(WalletInfoAdapter());
|
||||
}
|
||||
|
||||
Hive.registerAdapter(WalletTypeAdapter());
|
||||
DB.instance.hive.registerAdapter(WalletTypeAdapter());
|
||||
|
||||
Hive.registerAdapter(UnspentCoinsInfoAdapter());
|
||||
await Hive.initFlutter(
|
||||
DB.instance.hive.registerAdapter(UnspentCoinsInfoAdapter());
|
||||
|
||||
DB.instance.hive.init(
|
||||
(await StackFileSystem.applicationHiveDirectory()).path,
|
||||
);
|
||||
|
||||
await Hive.openBox<dynamic>(DB.boxNameDBInfo);
|
||||
await Hive.openBox<dynamic>(DB.boxNamePrefs);
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNameDBInfo);
|
||||
await DB.instance.hive.openBox<dynamic>(DB.boxNamePrefs);
|
||||
await Prefs.instance.init();
|
||||
|
||||
if (AppConfig.appName == "Campfire" &&
|
||||
!Util.isDesktop &&
|
||||
!CampfireMigration.didRun) {
|
||||
await CampfireMigration.init();
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// This should be moved to happen during the loading animation instead of
|
||||
// showing a blank screen for 4-10 seconds.
|
||||
|
@ -792,7 +800,13 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
biometricsCancelButtonString: "Cancel",
|
||||
);
|
||||
} else {
|
||||
return const IntroView();
|
||||
if (AppConfig.appName == "Campfire" &&
|
||||
!CampfireMigration.didRun &&
|
||||
CampfireMigration.hasOldWallets) {
|
||||
return const CampfireMigrateView();
|
||||
} else {
|
||||
return const IntroView();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// CURRENTLY DISABLED as cannot be animated
|
||||
|
|
|
@ -96,7 +96,8 @@ class _NameYourWalletViewState extends ConsumerState<NameYourWalletView> {
|
|||
|
||||
if (mounted) {
|
||||
ref.read(mnemonicWordCountStateProvider.state).state =
|
||||
coin.possibleMnemonicLengths.last;
|
||||
coin.defaultSeedPhraseLength;
|
||||
|
||||
ref.read(pNewWalletOptions.notifier).state = null;
|
||||
|
||||
switch (widget.addWalletType) {
|
||||
|
|
|
@ -170,6 +170,11 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
|
||||
final lengths = coin.possibleMnemonicLengths;
|
||||
|
||||
final isMoneroAnd25 = coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state == 25;
|
||||
final isWowneroAnd25 = coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state == 25;
|
||||
|
||||
return MasterScaffold(
|
||||
isDesktop: isDesktop,
|
||||
appBar: isDesktop
|
||||
|
@ -222,13 +227,7 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
SizedBox(
|
||||
height: isDesktop ? 40 : 24,
|
||||
),
|
||||
if ((coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25) ||
|
||||
coin is Epiccash ||
|
||||
(coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25))
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
Text(
|
||||
"Choose start date",
|
||||
style: isDesktop
|
||||
|
@ -240,58 +239,28 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
if ((coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25) ||
|
||||
coin is Epiccash ||
|
||||
(coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25))
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
SizedBox(
|
||||
height: isDesktop ? 16 : 8,
|
||||
),
|
||||
if ((coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25) ||
|
||||
coin is Epiccash ||
|
||||
(coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25))
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
if (!isDesktop)
|
||||
RestoreFromDatePicker(
|
||||
onTap: chooseDate,
|
||||
controller: _dateController,
|
||||
),
|
||||
if ((coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25) ||
|
||||
coin is Epiccash ||
|
||||
(coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25))
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
if (isDesktop)
|
||||
// TODO desktop date picker
|
||||
RestoreFromDatePicker(
|
||||
onTap: chooseDesktopDate,
|
||||
controller: _dateController,
|
||||
),
|
||||
if ((coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25) ||
|
||||
coin is Epiccash ||
|
||||
(coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25))
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if ((coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25) ||
|
||||
coin is Epiccash ||
|
||||
(coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25))
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
|
@ -308,13 +277,7 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if ((coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25) ||
|
||||
coin is Epiccash ||
|
||||
(coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25))
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
SizedBox(
|
||||
height: isDesktop ? 24 : 16,
|
||||
),
|
||||
|
|
|
@ -179,8 +179,14 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
bool _isValidMnemonicWord(String word) {
|
||||
// TODO: get the actual language
|
||||
if (widget.coin is Monero) {
|
||||
final moneroWordList = libxmr.monero.getMoneroWordList("English");
|
||||
return moneroWordList.contains(word);
|
||||
switch (widget.seedWordsLength) {
|
||||
case 25:
|
||||
return libxmr.monero.getMoneroWordList("English").contains(word);
|
||||
case 16:
|
||||
return Monero.sixteenWordsWordList.contains(word);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (widget.coin is Wownero) {
|
||||
final wowneroWordList = libwow.wownero.getWowneroWordList(
|
||||
|
|
210
lib/pages/campfire_migrate_view.dart
Normal file
210
lib/pages/campfire_migrate_view.dart
Normal file
|
@ -0,0 +1,210 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../db/special_migrations.dart';
|
||||
import '../themes/stack_colors.dart';
|
||||
import '../utilities/text_styles.dart';
|
||||
import '../utilities/util.dart';
|
||||
import '../widgets/app_icon.dart';
|
||||
import '../widgets/background.dart';
|
||||
import '../widgets/custom_buttons/blue_text_button.dart';
|
||||
import '../widgets/custom_buttons/checkbox_text_button.dart';
|
||||
import '../widgets/desktop/primary_button.dart';
|
||||
import '../widgets/loading_indicator.dart';
|
||||
import '../widgets/rounded_container.dart';
|
||||
import 'add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart';
|
||||
import 'intro_view.dart';
|
||||
|
||||
class CampfireMigrateView extends StatelessWidget {
|
||||
const CampfireMigrateView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
child: AppIcon(
|
||||
width: 50,
|
||||
height: 50,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Your old Campfire wallets are listed below. "
|
||||
"If you would like to keep them then copy the mnemonics "
|
||||
"somewhere safe so you can restore them.",
|
||||
style: STextStyles.w600_12(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FutureBuilder(
|
||||
future: CampfireMigration.fetch(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done) {
|
||||
final count = (snapshot.data?.length ?? 0) + 1;
|
||||
|
||||
return ListView.separated(
|
||||
itemCount: count,
|
||||
separatorBuilder: (_, __) => const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
itemBuilder: (_, index) => index == count - 1
|
||||
? const _ContinueButtonGroup()
|
||||
: _CampfireWallet(
|
||||
mnemonic: snapshot.data![index].$2,
|
||||
name: snapshot.data![index].$1,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
LoadingIndicator(
|
||||
width: 100,
|
||||
height: 100,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ContinueButtonGroup extends StatefulWidget {
|
||||
const _ContinueButtonGroup({super.key});
|
||||
|
||||
@override
|
||||
State<_ContinueButtonGroup> createState() => _ContinueButtonGroupState();
|
||||
}
|
||||
|
||||
class _ContinueButtonGroupState extends State<_ContinueButtonGroup> {
|
||||
bool _checked = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
CheckboxTextButton(
|
||||
label: "I have saved all my mnemonics and double checked each one",
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_checked = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
PrimaryButton(
|
||||
enabled: _checked,
|
||||
label: "Continue",
|
||||
onPressed: () {
|
||||
CampfireMigration.setDidRun();
|
||||
// could do pushReplacementNamed but we won't show this again on next run anyways
|
||||
Navigator.of(context).pushNamed(
|
||||
IntroView.routeName,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CampfireWallet extends StatefulWidget {
|
||||
const _CampfireWallet({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.mnemonic,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final List<String> mnemonic;
|
||||
|
||||
@override
|
||||
State<_CampfireWallet> createState() => _CampfireWalletState();
|
||||
}
|
||||
|
||||
class _CampfireWalletState extends State<_CampfireWallet> {
|
||||
bool _show = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoundedContainer(
|
||||
color: Theme.of(context).extension<StackColors>()!.background,
|
||||
borderColor: Theme.of(context).extension<StackColors>()!.textDark,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
widget.name,
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
CustomTextButton(
|
||||
text: "Copy mnemonic",
|
||||
onTap: () => Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: widget.mnemonic.join(" "),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
_show
|
||||
? MnemonicTable(
|
||||
words: widget.mnemonic,
|
||||
isDesktop: Util.isDesktop,
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: PrimaryButton(
|
||||
label: "Show mnemonic",
|
||||
onPressed: () => setState(() => _show = true),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -13,7 +13,6 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
import '../../db/hive/db.dart';
|
||||
|
@ -52,7 +51,7 @@ class _ForgotPasswordDesktopViewState
|
|||
final appRoot = await StackFileSystem.applicationRootDirectory();
|
||||
|
||||
try {
|
||||
await Hive.close();
|
||||
await DB.instance.hive.close();
|
||||
if (Platform.isWindows) {
|
||||
final xmrDir = Directory("${appRoot.path}/wallets");
|
||||
if (xmrDir.existsSync()) {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:nanodart/nanodart.dart';
|
||||
|
||||
import '../networking/http.dart';
|
||||
import 'tor_service.dart';
|
||||
import '../utilities/prefs.dart';
|
||||
import 'tor_service.dart';
|
||||
|
||||
class NanoAPI {
|
||||
static Future<
|
||||
|
@ -14,6 +15,7 @@ class NanoAPI {
|
|||
required Uri server,
|
||||
required bool representative,
|
||||
required String account,
|
||||
required Map<String, String> headers,
|
||||
}) async {
|
||||
NAccountInfo? accountInfo;
|
||||
Exception? exception;
|
||||
|
@ -23,9 +25,7 @@ class NanoAPI {
|
|||
try {
|
||||
final response = await client.post(
|
||||
url: server,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: headers,
|
||||
body: jsonEncode({
|
||||
"action": "account_info",
|
||||
"representative": "true",
|
||||
|
@ -64,6 +64,7 @@ class NanoAPI {
|
|||
required String balance,
|
||||
required String privateKey,
|
||||
required String work,
|
||||
required Map<String, String> headers,
|
||||
}) async {
|
||||
final Map<String, String> block = {
|
||||
"type": "state",
|
||||
|
@ -98,7 +99,11 @@ class NanoAPI {
|
|||
|
||||
block["signature"] = signature;
|
||||
|
||||
final map = await postBlock(server: server, block: block);
|
||||
final map = await postBlock(
|
||||
server: server,
|
||||
block: block,
|
||||
headers: headers,
|
||||
);
|
||||
|
||||
if (map is Map && map["error"] != null) {
|
||||
throw Exception(map["error"].toString());
|
||||
|
@ -111,14 +116,13 @@ class NanoAPI {
|
|||
static Future<dynamic> postBlock({
|
||||
required Uri server,
|
||||
required Map<String, dynamic> block,
|
||||
required Map<String, String> headers,
|
||||
}) async {
|
||||
final HTTP client = HTTP();
|
||||
|
||||
final response = await client.post(
|
||||
url: server,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: headers,
|
||||
body: jsonEncode({
|
||||
"action": "process",
|
||||
"json_block": "true",
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:hive/hive.dart' show Box;
|
||||
import 'package:stack_wallet_backup/secure_storage.dart';
|
||||
|
||||
import '../db/hive/db.dart';
|
||||
import 'logger.dart';
|
||||
|
||||
const String kBoxNameDesktopData = "desktopData";
|
||||
|
@ -185,7 +186,7 @@ class DPS {
|
|||
Future<void> _put({required String key, required String value}) async {
|
||||
Box<String>? box;
|
||||
try {
|
||||
box = await Hive.openBox<String>(kBoxNameDesktopData);
|
||||
box = await DB.instance.hive.openBox<String>(kBoxNameDesktopData);
|
||||
await box.put(key, value);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
|
@ -201,7 +202,7 @@ class DPS {
|
|||
String? value;
|
||||
Box<String>? box;
|
||||
try {
|
||||
box = await Hive.openBox<String>(kBoxNameDesktopData);
|
||||
box = await DB.instance.hive.openBox<String>(kBoxNameDesktopData);
|
||||
value = box.get(key);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
|
@ -217,6 +218,6 @@ class DPS {
|
|||
/// Dangerous. Used in one place and should not be called anywhere else.
|
||||
@Deprecated("Don't use this if at all possible")
|
||||
Future<void> deleteBox() async {
|
||||
await Hive.deleteBoxFromDisk(kBoxNameDesktopData);
|
||||
await DB.instance.hive.deleteBoxFromDisk(kBoxNameDesktopData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,9 @@ abstract class StackFileSystem {
|
|||
_overrideDirSet = true;
|
||||
}
|
||||
|
||||
static bool get _createSubDirs =>
|
||||
Util.isDesktop || AppConfig.appName == "Campfire";
|
||||
|
||||
static Future<Directory> applicationRootDirectory() async {
|
||||
Directory appDirectory;
|
||||
|
||||
|
@ -80,7 +83,7 @@ abstract class StackFileSystem {
|
|||
|
||||
static Future<Directory> applicationIsarDirectory() async {
|
||||
final root = await applicationRootDirectory();
|
||||
if (Util.isDesktop) {
|
||||
if (_createSubDirs) {
|
||||
final dir = Directory("${root.path}/isar");
|
||||
if (!dir.existsSync()) {
|
||||
await dir.create();
|
||||
|
@ -94,7 +97,7 @@ abstract class StackFileSystem {
|
|||
// Not used in general now. See applicationFiroCacheSQLiteDirectory()
|
||||
// static Future<Directory> applicationSQLiteDirectory() async {
|
||||
// final root = await applicationRootDirectory();
|
||||
// if (Util.isDesktop) {
|
||||
// if (_createSubDirs) {
|
||||
// final dir = Directory("${root.path}/sqlite");
|
||||
// if (!dir.existsSync()) {
|
||||
// await dir.create();
|
||||
|
@ -107,7 +110,7 @@ abstract class StackFileSystem {
|
|||
|
||||
static Future<Directory> applicationTorDirectory() async {
|
||||
final root = await applicationRootDirectory();
|
||||
if (Util.isDesktop) {
|
||||
if (_createSubDirs) {
|
||||
final dir = Directory("${root.path}/tor");
|
||||
if (!dir.existsSync()) {
|
||||
await dir.create();
|
||||
|
@ -120,7 +123,7 @@ abstract class StackFileSystem {
|
|||
|
||||
static Future<Directory> applicationFiroCacheSQLiteDirectory() async {
|
||||
final root = await applicationRootDirectory();
|
||||
if (Util.isDesktop) {
|
||||
if (_createSubDirs) {
|
||||
final dir = Directory("${root.path}/sqlite/firo_cache");
|
||||
if (!dir.existsSync()) {
|
||||
await dir.create(recursive: true);
|
||||
|
@ -133,7 +136,7 @@ abstract class StackFileSystem {
|
|||
|
||||
static Future<Directory> applicationHiveDirectory() async {
|
||||
final root = await applicationRootDirectory();
|
||||
if (Util.isDesktop) {
|
||||
if (_createSubDirs) {
|
||||
final dir = Directory("${root.path}/hive");
|
||||
if (!dir.existsSync()) {
|
||||
await dir.create();
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'dart:convert';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:socks5_proxy/socks.dart';
|
||||
|
||||
import '../widgets/desktop/primary_button.dart';
|
||||
import '../widgets/desktop/secondary_button.dart';
|
||||
|
@ -30,12 +31,26 @@ class MoneroNodeConnectionResponse {
|
|||
|
||||
Future<MoneroNodeConnectionResponse> testMoneroNodeConnection(
|
||||
Uri uri,
|
||||
bool allowBadX509Certificate,
|
||||
) async {
|
||||
final client = HttpClient();
|
||||
bool allowBadX509Certificate, {
|
||||
required ({
|
||||
InternetAddress host,
|
||||
int port,
|
||||
})? proxyInfo,
|
||||
}) async {
|
||||
final httpClient = HttpClient();
|
||||
MoneroNodeConnectionResponse? badCertResponse;
|
||||
|
||||
try {
|
||||
client.badCertificateCallback = (cert, url, port) {
|
||||
if (proxyInfo != null) {
|
||||
SocksTCPClient.assignToHttpClient(httpClient, [
|
||||
ProxySettings(
|
||||
proxyInfo.host,
|
||||
proxyInfo.port,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
httpClient.badCertificateCallback = (cert, url, port) {
|
||||
if (allowBadX509Certificate) {
|
||||
return true;
|
||||
}
|
||||
|
@ -49,7 +64,7 @@ Future<MoneroNodeConnectionResponse> testMoneroNodeConnection(
|
|||
return false;
|
||||
};
|
||||
|
||||
final request = await client.postUrl(uri);
|
||||
final request = await httpClient.postUrl(uri);
|
||||
|
||||
final body = utf8.encode(
|
||||
jsonEncode({
|
||||
|
@ -85,7 +100,7 @@ Future<MoneroNodeConnectionResponse> testMoneroNodeConnection(
|
|||
return MoneroNodeConnectionResponse(null, null, null, false);
|
||||
}
|
||||
} finally {
|
||||
client.close(force: true);
|
||||
httpClient.close(force: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +1,35 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:solana/solana.dart';
|
||||
|
||||
import '../networking/http.dart';
|
||||
import '../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
|
||||
import '../providers/global/prefs_provider.dart';
|
||||
import '../services/tor_service.dart';
|
||||
import '../wallets/api/tezos/tezos_rpc_api.dart';
|
||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart';
|
||||
import '../wallets/crypto_currency/intermediate/cryptonote_currency.dart';
|
||||
import '../wallets/crypto_currency/intermediate/nano_currency.dart';
|
||||
import 'connection_check/electrum_connection_check.dart';
|
||||
import 'logger.dart';
|
||||
import 'test_epic_box_connection.dart';
|
||||
import 'test_eth_node_connection.dart';
|
||||
import 'test_monero_node_connection.dart';
|
||||
import 'test_stellar_node_connection.dart';
|
||||
import '../wallets/api/tezos/tezos_rpc_api.dart';
|
||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart';
|
||||
import '../wallets/crypto_currency/intermediate/cryptonote_currency.dart';
|
||||
import '../wallets/crypto_currency/intermediate/nano_currency.dart';
|
||||
|
||||
Future<bool> _xmrHelper(
|
||||
NodeFormData nodeFormData,
|
||||
BuildContext context,
|
||||
void Function(NodeFormData)? onSuccess,
|
||||
({
|
||||
InternetAddress host,
|
||||
int port,
|
||||
})? proxyInfo,
|
||||
) async {
|
||||
final data = nodeFormData;
|
||||
final url = data.host!;
|
||||
|
@ -36,6 +44,7 @@ Future<bool> _xmrHelper(
|
|||
final response = await testMoneroNodeConnection(
|
||||
Uri.parse(uriString),
|
||||
false,
|
||||
proxyInfo: proxyInfo,
|
||||
);
|
||||
|
||||
if (response.cert != null) {
|
||||
|
@ -48,8 +57,11 @@ Future<bool> _xmrHelper(
|
|||
);
|
||||
|
||||
if (shouldAllowBadCert) {
|
||||
final response =
|
||||
await testMoneroNodeConnection(Uri.parse(uriString), true);
|
||||
final response = await testMoneroNodeConnection(
|
||||
Uri.parse(uriString),
|
||||
true,
|
||||
proxyInfo: proxyInfo,
|
||||
);
|
||||
onSuccess?.call(data..host = url);
|
||||
return response.success;
|
||||
}
|
||||
|
@ -90,6 +102,10 @@ Future<bool> testNodeConnection({
|
|||
|
||||
case CryptonoteCurrency():
|
||||
try {
|
||||
final proxyInfo = ref.read(prefsChangeNotifierProvider).useTor
|
||||
? ref.read(pTorService).getProxyInfo()
|
||||
: null;
|
||||
|
||||
final url = formData.host!;
|
||||
final uri = Uri.tryParse(url);
|
||||
if (uri != null) {
|
||||
|
@ -101,9 +117,10 @@ Future<bool> testNodeConnection({
|
|||
..useSSL = true,
|
||||
context,
|
||||
onSuccess,
|
||||
proxyInfo,
|
||||
);
|
||||
|
||||
if (testPassed == false) {
|
||||
if (testPassed == false && context.mounted) {
|
||||
// try http
|
||||
testPassed = await _xmrHelper(
|
||||
formData
|
||||
|
@ -111,6 +128,7 @@ Future<bool> testNodeConnection({
|
|||
..useSSL = false,
|
||||
context,
|
||||
onSuccess,
|
||||
proxyInfo,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
@ -120,6 +138,7 @@ Future<bool> testNodeConnection({
|
|||
..useSSL = true,
|
||||
context,
|
||||
onSuccess,
|
||||
proxyInfo,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -161,8 +180,25 @@ Future<bool> testNodeConnection({
|
|||
break;
|
||||
|
||||
case NanoCurrency():
|
||||
//TODO: check network/node
|
||||
throw UnimplementedError();
|
||||
try {
|
||||
final uri = Uri.parse(formData.host!);
|
||||
|
||||
final response = await HTTP().post(
|
||||
url: uri,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: jsonEncode(
|
||||
{
|
||||
"action": "version",
|
||||
},
|
||||
),
|
||||
proxyInfo: ref.read(prefsChangeNotifierProvider).useTor
|
||||
? ref.read(pTorService).getProxyInfo()
|
||||
: null,
|
||||
);
|
||||
|
||||
testPassed = response.code == 200;
|
||||
} catch (_) {}
|
||||
break;
|
||||
|
||||
case Tezos():
|
||||
try {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:nanodart/nanodart.dart';
|
||||
|
||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||
import '../../../models/node_model.dart';
|
||||
import '../../../utilities/default_nodes.dart';
|
||||
|
@ -66,7 +67,8 @@ class Banano extends NanoCurrency {
|
|||
switch (network) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
return NodeModel(
|
||||
host: "https://kaliumapi.appditto.com/api",
|
||||
// host: "https://kaliumapi.appditto.com/api",
|
||||
host: "https://nodes.nanswap.com/BAN",
|
||||
port: 443,
|
||||
name: DefaultNodes.defaultName,
|
||||
id: DefaultNodes.buildId(this),
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,5 @@
|
|||
import 'package:nanodart/nanodart.dart';
|
||||
|
||||
import '../../../models/isar/models/isar_models.dart';
|
||||
import '../../../models/node_model.dart';
|
||||
import '../../../utilities/default_nodes.dart';
|
||||
|
@ -66,7 +67,8 @@ class Nano extends NanoCurrency {
|
|||
switch (network) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
return NodeModel(
|
||||
host: "https://rainstorm.city/api",
|
||||
// host: "https://rainstorm.city/api",
|
||||
host: "https://nodes.nanswap.com/XNO",
|
||||
port: 443,
|
||||
name: DefaultNodes.defaultName,
|
||||
id: DefaultNodes.buildId(this),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:cw_wownero/api/wallet.dart' as wownero_wallet;
|
||||
|
||||
import '../../../models/node_model.dart';
|
||||
import '../../../utilities/default_nodes.dart';
|
||||
import '../../../utilities/enums/derive_path_type_enum.dart';
|
||||
|
@ -42,6 +43,9 @@ class Wownero extends CryptonoteCurrency {
|
|||
@override
|
||||
int get minConfirms => 15;
|
||||
|
||||
@override
|
||||
bool get torSupport => true;
|
||||
|
||||
@override
|
||||
bool validateAddress(String address) {
|
||||
return wownero_wallet.addressValid(address);
|
||||
|
|
|
@ -105,6 +105,13 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
|||
}
|
||||
}
|
||||
|
||||
final missing = await getMissingSparkSpendTransactionIds();
|
||||
for (final txid in missing.map((e) => e.txid).toSet()) {
|
||||
allTxHashes.add({
|
||||
"tx_hash": txid,
|
||||
});
|
||||
}
|
||||
|
||||
final List<Map<String, dynamic>> allTransactions = [];
|
||||
|
||||
// some lelantus transactions aren't fetched via wallet addresses so they
|
||||
|
@ -187,12 +194,30 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
|||
final bool isMasterNodePayment = false;
|
||||
final bool isSparkSpend = txData["type"] == 9 && txData["version"] == 3;
|
||||
final bool isMySpark = sparkTxids.contains(txData["txid"] as String);
|
||||
final bool isMySpentSpark =
|
||||
missing.where((e) => e.txid == txData["txid"]).isNotEmpty;
|
||||
|
||||
final sparkCoinsInvolved =
|
||||
sparkCoins.where((e) => e.txHash == txData["txid"]);
|
||||
if (isMySpark && sparkCoinsInvolved.isEmpty) {
|
||||
final sparkCoinsInvolvedReceived = sparkCoins.where(
|
||||
(e) =>
|
||||
e.txHash == txData["txid"] ||
|
||||
missing.where((f) => e.lTagHash == f.tag).isNotEmpty,
|
||||
);
|
||||
|
||||
final sparkCoinsInvolvedSpent = sparkCoins.where(
|
||||
(e) => missing.where((f) => e.lTagHash == f.tag).isNotEmpty,
|
||||
);
|
||||
|
||||
if (isMySpark && sparkCoinsInvolvedReceived.isEmpty && !isMySpentSpark) {
|
||||
Logging.instance.log(
|
||||
"sparkCoinsInvolved is empty and should not be! (ignoring tx parsing)",
|
||||
"sparkCoinsInvolvedReceived is empty and should not be! (ignoring tx parsing)",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isMySpentSpark && sparkCoinsInvolvedSpent.isEmpty && !isMySpark) {
|
||||
Logging.instance.log(
|
||||
"sparkCoinsInvolvedSpent is empty and should not be! (ignoring tx parsing)",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
continue;
|
||||
|
@ -267,7 +292,7 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
|||
final serCoin = base64Encode(
|
||||
output.scriptPubKeyHex.substring(2, 488).toUint8ListFromHex,
|
||||
);
|
||||
final coin = sparkCoinsInvolved
|
||||
final coin = sparkCoinsInvolvedReceived
|
||||
.where((e) => e.serializedCoinB64!.startsWith(serCoin))
|
||||
.firstOrNull;
|
||||
|
||||
|
@ -343,7 +368,7 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
|||
);
|
||||
}
|
||||
|
||||
if (isSparkSpend) {
|
||||
void parseAnonFees() {
|
||||
// anon fees
|
||||
final nFee = Decimal.tryParse(map["nFees"].toString());
|
||||
if (nFee != null) {
|
||||
|
@ -354,6 +379,22 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
|||
|
||||
anonFees = anonFees! + fees;
|
||||
}
|
||||
}
|
||||
|
||||
List<SparkCoin>? spentSparkCoins;
|
||||
|
||||
if (isMySpentSpark) {
|
||||
parseAnonFees();
|
||||
final tags = await FiroCacheCoordinator.getUsedCoinTagsFor(
|
||||
txid: txData["txid"] as String,
|
||||
);
|
||||
spentSparkCoins = sparkCoinsInvolvedSpent
|
||||
.where(
|
||||
(e) => tags.contains(e.lTagHash),
|
||||
)
|
||||
.toList();
|
||||
} else if (isSparkSpend) {
|
||||
parseAnonFees();
|
||||
} else if (isSparkMint) {
|
||||
final address = map["address"] as String?;
|
||||
final value = map["valueSat"] as int?;
|
||||
|
@ -444,6 +485,18 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
|||
wasSentFromThisWallet = true;
|
||||
}
|
||||
}
|
||||
} else if (isMySpentSpark &&
|
||||
spentSparkCoins != null &&
|
||||
spentSparkCoins.isNotEmpty) {
|
||||
input = input.copyWith(
|
||||
addresses: spentSparkCoins.map((e) => e.address).toList(),
|
||||
valueStringSats: spentSparkCoins
|
||||
.map((e) => e.value)
|
||||
.fold(BigInt.zero, (p, e) => p + e)
|
||||
.toString(),
|
||||
walletOwns: true,
|
||||
);
|
||||
wasSentFromThisWallet = true;
|
||||
}
|
||||
|
||||
inputs.add(input);
|
||||
|
@ -514,7 +567,7 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
|||
if (anonFees != null) {
|
||||
otherData = jsonEncode(
|
||||
{
|
||||
"overrideFee": anonFees.toJsonString(),
|
||||
"overrideFee": anonFees!.toJsonString(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,9 @@ import 'dart:convert';
|
|||
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:nanodart/nanodart.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../../external_api_keys.dart';
|
||||
import '../../../models/balance.dart';
|
||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||
import '../../../models/isar/models/blockchain_data/transaction.dart';
|
||||
|
@ -18,9 +21,20 @@ import '../../../utilities/logger.dart';
|
|||
import '../../crypto_currency/intermediate/nano_currency.dart';
|
||||
import '../../models/tx_data.dart';
|
||||
import '../intermediate/bip39_wallet.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
const _kWorkServer = "https://rpc.nano.to";
|
||||
// const _kWorkServer = "https://rpc.nano.to";
|
||||
const _kWorkServer = "https://nodes.nanswap.com/XNO";
|
||||
|
||||
Map<String, String> _buildHeaders(String url) {
|
||||
final result = {
|
||||
'Content-type': 'application/json',
|
||||
};
|
||||
if (url
|
||||
case "https://nodes.nanswap.com/XNO" || "https://nodes.nanswap.com/BAN") {
|
||||
result["nodes-api-key"] = kNanoSwapRpcApiKey;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
||||
// since nano based coins only have a single address/account we can cache
|
||||
|
@ -36,7 +50,7 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
return _httpClient
|
||||
.post(
|
||||
url: Uri.parse(_kWorkServer), // this should be a
|
||||
headers: {'Content-type': 'application/json'},
|
||||
headers: _buildHeaders(_kWorkServer),
|
||||
body: json.encode(
|
||||
{
|
||||
"action": "work_generate",
|
||||
|
@ -93,10 +107,6 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
// TODO: the opening block of an account is a special case
|
||||
bool openBlock = false;
|
||||
|
||||
final headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
// first check if the account is open:
|
||||
// get the account info (we need the frontier and representative):
|
||||
final infoBody = jsonEncode({
|
||||
|
@ -104,9 +114,10 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
"representative": "true",
|
||||
"account": publicAddress,
|
||||
});
|
||||
final node = getCurrentNode();
|
||||
final infoResponse = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: headers,
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: infoBody,
|
||||
proxyInfo: prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
);
|
||||
|
@ -124,8 +135,8 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
});
|
||||
|
||||
final balanceResponse = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: headers,
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: balanceBody,
|
||||
proxyInfo: prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
);
|
||||
|
@ -198,8 +209,8 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
"block": receiveBlock,
|
||||
});
|
||||
final processResponse = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: headers,
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: processBody,
|
||||
proxyInfo: prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
);
|
||||
|
@ -212,14 +223,14 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
}
|
||||
|
||||
Future<void> _confirmAllReceivable(String accountAddress) async {
|
||||
final node = getCurrentNode();
|
||||
final receivableResponse = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: {"Content-Type": "application/json"},
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: jsonEncode({
|
||||
"action": "receivable",
|
||||
"source": "true",
|
||||
"account": accountAddress,
|
||||
"count": "-1",
|
||||
}),
|
||||
proxyInfo: prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
);
|
||||
|
@ -247,10 +258,12 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
final address =
|
||||
(_cachedAddress ?? await getCurrentReceivingAddress())!.value;
|
||||
|
||||
final node = getCurrentNode();
|
||||
final response = await NanoAPI.getAccountInfo(
|
||||
server: serverURI,
|
||||
representative: true,
|
||||
account: address,
|
||||
headers: _buildHeaders(node.host),
|
||||
);
|
||||
|
||||
return response.accountInfo?.representative ??
|
||||
|
@ -259,7 +272,8 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
|
||||
Future<bool> changeRepresentative(String newRepresentative) async {
|
||||
try {
|
||||
final serverURI = Uri.parse(getCurrentNode().host);
|
||||
final node = getCurrentNode();
|
||||
final serverURI = Uri.parse(node.host);
|
||||
await updateBalance();
|
||||
final balance = info.cachedBalance.spendable.raw.toString();
|
||||
final String privateKey = await _getPrivateKeyFromMnemonic();
|
||||
|
@ -270,6 +284,7 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
server: serverURI,
|
||||
representative: true,
|
||||
account: address,
|
||||
headers: _buildHeaders(node.host),
|
||||
);
|
||||
|
||||
if (response.accountInfo == null) {
|
||||
|
@ -287,6 +302,7 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
balance: balance,
|
||||
privateKey: privateKey,
|
||||
work: work!,
|
||||
headers: _buildHeaders(node.host),
|
||||
);
|
||||
} catch (_) {
|
||||
rethrow;
|
||||
|
@ -331,11 +347,11 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
|
||||
@override
|
||||
Future<bool> pingCheck() async {
|
||||
final uri = Uri.parse(getCurrentNode().host);
|
||||
|
||||
final node = getCurrentNode();
|
||||
final uri = Uri.parse(node.host);
|
||||
final response = await _httpClient.post(
|
||||
url: uri,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
headers: _buildHeaders(node.host),
|
||||
body: jsonEncode(
|
||||
{
|
||||
"action": "version",
|
||||
|
@ -384,13 +400,10 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
"account": publicAddress,
|
||||
});
|
||||
|
||||
final headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
final node = getCurrentNode();
|
||||
final infoResponse = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: headers,
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: infoBody,
|
||||
proxyInfo:
|
||||
prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
|
@ -443,8 +456,8 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
"block": sendBlock,
|
||||
});
|
||||
final processResponse = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: headers,
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: processBody,
|
||||
proxyInfo:
|
||||
prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
|
@ -485,6 +498,49 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
}
|
||||
}
|
||||
|
||||
// recurse over api calls if required
|
||||
// (if more than 200 history items)
|
||||
Future<Map<String, dynamic>> _fetchAll(
|
||||
String publicAddress,
|
||||
String? previous,
|
||||
Map<String, dynamic>? data,
|
||||
) async {
|
||||
final node = getCurrentNode();
|
||||
final body = {
|
||||
"action": "account_history",
|
||||
"account": publicAddress,
|
||||
"count": "200",
|
||||
};
|
||||
|
||||
if (previous is String) {
|
||||
body["head"] = previous;
|
||||
}
|
||||
|
||||
final response = await _httpClient.post(
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: jsonEncode(body),
|
||||
proxyInfo: prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
);
|
||||
|
||||
// this should really have proper type checking and error propagation but I'm out of time
|
||||
final newData =
|
||||
Map<String, dynamic>.from((await jsonDecode(response.body)) as Map);
|
||||
|
||||
if (newData["previous"] is String) {
|
||||
if (data?["history"] is List) {
|
||||
(newData["history"] as List).addAll(data!["history"] as List);
|
||||
}
|
||||
return await _fetchAll(
|
||||
publicAddress,
|
||||
newData["previous"] as String,
|
||||
newData,
|
||||
);
|
||||
}
|
||||
|
||||
return newData;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateTransactions() async {
|
||||
await updateChainHeight();
|
||||
|
@ -492,17 +548,9 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
(_cachedAddress ?? await getCurrentReceivingAddress())!;
|
||||
final String publicAddress = receivingAddress.value;
|
||||
await _confirmAllReceivable(publicAddress);
|
||||
final response = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: jsonEncode({
|
||||
"action": "account_history",
|
||||
"account": publicAddress,
|
||||
"count": "-1",
|
||||
}),
|
||||
proxyInfo: prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
);
|
||||
final data = await jsonDecode(response.body);
|
||||
|
||||
final data = await _fetchAll(publicAddress, null, null);
|
||||
|
||||
final transactions = data["history"] is List
|
||||
? data["history"] as List<dynamic>
|
||||
: <dynamic>[];
|
||||
|
@ -571,13 +619,11 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
"action": "account_balance",
|
||||
"account": addressString,
|
||||
});
|
||||
final headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
final node = getCurrentNode();
|
||||
final response = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: headers,
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: body,
|
||||
proxyInfo:
|
||||
prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
|
@ -622,12 +668,11 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
"action": "account_info",
|
||||
"account": publicAddress,
|
||||
});
|
||||
final headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
final node = getCurrentNode();
|
||||
final infoResponse = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: headers,
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: infoBody,
|
||||
proxyInfo:
|
||||
prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
|
|
|
@ -696,6 +696,32 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
}
|
||||
|
||||
Future<Set<LTagPair>> getMissingSparkSpendTransactionIds() async {
|
||||
final tags = await mainDB.isar.sparkCoins
|
||||
.where()
|
||||
.walletIdEqualToAnyLTagHash(walletId)
|
||||
.filter()
|
||||
.isUsedEqualTo(true)
|
||||
.lTagHashProperty()
|
||||
.findAll();
|
||||
|
||||
final usedCoinTxidsFoundLocally = await mainDB.isar.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.subTypeEqualTo(TransactionSubType.sparkSpend)
|
||||
.txidProperty()
|
||||
.findAll();
|
||||
|
||||
final pairs = await FiroCacheCoordinator.getUsedCoinTxidsFor(
|
||||
tags: tags,
|
||||
);
|
||||
|
||||
pairs.removeWhere((e) => usedCoinTxidsFoundLocally.contains(e.txid));
|
||||
|
||||
return pairs.toSet();
|
||||
}
|
||||
|
||||
Future<void> refreshSparkBalance() async {
|
||||
final currentHeight = await chainHeight;
|
||||
final unusedCoins = await mainDB.isar.sparkCoins
|
||||
|
|
|
@ -117,9 +117,12 @@ class NodeOptionsSheet extends ConsumerWidget {
|
|||
final response = await testMoneroNodeConnection(
|
||||
Uri.parse(uriString),
|
||||
false,
|
||||
proxyInfo: ref.read(prefsChangeNotifierProvider).useTor
|
||||
? ref.read(pTorService).getProxyInfo()
|
||||
: null,
|
||||
);
|
||||
|
||||
if (response.cert != null) {
|
||||
if (response.cert != null && context.mounted) {
|
||||
// if (mounted) {
|
||||
final shouldAllowBadCert = await showBadX509CertificateDialog(
|
||||
response.cert!,
|
||||
|
@ -129,8 +132,13 @@ class NodeOptionsSheet extends ConsumerWidget {
|
|||
);
|
||||
|
||||
if (shouldAllowBadCert) {
|
||||
final response =
|
||||
await testMoneroNodeConnection(Uri.parse(uriString), true);
|
||||
final response = await testMoneroNodeConnection(
|
||||
Uri.parse(uriString),
|
||||
true,
|
||||
proxyInfo: ref.read(prefsChangeNotifierProvider).useTor
|
||||
? ref.read(pTorService).getProxyInfo()
|
||||
: null,
|
||||
);
|
||||
testPassed = response.success;
|
||||
}
|
||||
// }
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
|
||||
import '../../db/hive/db.dart';
|
||||
import '../../utilities/assets.dart';
|
||||
|
@ -17,7 +16,8 @@ const _kOneTimeTorHasBeenAddedDialogWasShown =
|
|||
Future<void> showOneTimeTorHasBeenAddedDialogIfRequired(
|
||||
BuildContext context,
|
||||
) async {
|
||||
final box = await Hive.openBox<bool>(DB.boxNameOneTimeDialogsShown);
|
||||
final box =
|
||||
await DB.instance.hive.openBox<bool>(DB.boxNameOneTimeDialogsShown);
|
||||
|
||||
if (!box.get(
|
||||
_kOneTimeTorHasBeenAddedDialogWasShown,
|
||||
|
@ -48,7 +48,9 @@ class _TorHasBeenAddedDialogState extends State<_TorHasBeenAddedDialog> {
|
|||
}
|
||||
_lock = true;
|
||||
try {
|
||||
final box = await Hive.openBox<bool>(DB.boxNameOneTimeDialogsShown);
|
||||
final box = await DB.instance.hive.openBox<bool>(
|
||||
DB.boxNameOneTimeDialogsShown,
|
||||
);
|
||||
await box.put(_kOneTimeTorHasBeenAddedDialogWasShown, true);
|
||||
} catch (_) {
|
||||
//
|
||||
|
|
|
@ -428,22 +428,6 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient {
|
|||
_i7.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i7.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i7.Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUnhashedUsedCoinsTags,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<List<String>>.value(<String>[]),
|
||||
) as _i7.Future<List<String>>);
|
||||
@override
|
||||
_i7.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
required List<String>? sparkCoinHashes,
|
||||
|
@ -471,6 +455,49 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient {
|
|||
returnValue: _i7.Future<int>.value(0),
|
||||
) as _i7.Future<int>);
|
||||
@override
|
||||
_i7.Future<Set<String>> getMempoolTxids({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMempoolTxids,
|
||||
[],
|
||||
{#requestID: requestID},
|
||||
),
|
||||
returnValue: _i7.Future<Set<String>>.value(<String>{}),
|
||||
) as _i7.Future<Set<String>>);
|
||||
@override
|
||||
_i7.Future<Map<String, dynamic>> getMempoolSparkData({
|
||||
String? requestID,
|
||||
required List<String>? txids,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMempoolSparkData,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#txids: txids,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i7.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i7.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i7.Future<List<List<dynamic>>> getSparkUnhashedUsedCoinsTagsWithTxHashes({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUnhashedUsedCoinsTagsWithTxHashes,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<List<List<dynamic>>>.value(<List<dynamic>>[]),
|
||||
) as _i7.Future<List<List<dynamic>>>);
|
||||
@override
|
||||
_i7.Future<Map<String, dynamic>> getFeeRate({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
|
|
@ -425,22 +425,6 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
|
|||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUnhashedUsedCoinsTags,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
required List<String>? sparkCoinHashes,
|
||||
|
@ -468,6 +452,49 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
|
|||
returnValue: _i6.Future<int>.value(0),
|
||||
) as _i6.Future<int>);
|
||||
@override
|
||||
_i6.Future<Set<String>> getMempoolTxids({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMempoolTxids,
|
||||
[],
|
||||
{#requestID: requestID},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getMempoolSparkData({
|
||||
String? requestID,
|
||||
required List<String>? txids,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMempoolSparkData,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#txids: txids,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<List<List<dynamic>>> getSparkUnhashedUsedCoinsTagsWithTxHashes({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUnhashedUsedCoinsTagsWithTxHashes,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<List<List<dynamic>>>.value(<List<dynamic>>[]),
|
||||
) as _i6.Future<List<List<dynamic>>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getFeeRate({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
|
|
@ -425,22 +425,6 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
|
|||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUnhashedUsedCoinsTags,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
required List<String>? sparkCoinHashes,
|
||||
|
@ -468,6 +452,49 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
|
|||
returnValue: _i6.Future<int>.value(0),
|
||||
) as _i6.Future<int>);
|
||||
@override
|
||||
_i6.Future<Set<String>> getMempoolTxids({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMempoolTxids,
|
||||
[],
|
||||
{#requestID: requestID},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getMempoolSparkData({
|
||||
String? requestID,
|
||||
required List<String>? txids,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMempoolSparkData,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#txids: txids,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<List<List<dynamic>>> getSparkUnhashedUsedCoinsTagsWithTxHashes({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUnhashedUsedCoinsTagsWithTxHashes,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<List<List<dynamic>>>.value(<List<dynamic>>[]),
|
||||
) as _i6.Future<List<List<dynamic>>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getFeeRate({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
|
|
@ -425,22 +425,6 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
|
|||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUnhashedUsedCoinsTags,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
required List<String>? sparkCoinHashes,
|
||||
|
@ -468,6 +452,49 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
|
|||
returnValue: _i6.Future<int>.value(0),
|
||||
) as _i6.Future<int>);
|
||||
@override
|
||||
_i6.Future<Set<String>> getMempoolTxids({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMempoolTxids,
|
||||
[],
|
||||
{#requestID: requestID},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getMempoolSparkData({
|
||||
String? requestID,
|
||||
required List<String>? txids,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMempoolSparkData,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#txids: txids,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<List<List<dynamic>>> getSparkUnhashedUsedCoinsTagsWithTxHashes({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUnhashedUsedCoinsTagsWithTxHashes,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<List<List<dynamic>>>.value(<List<dynamic>>[]),
|
||||
) as _i6.Future<List<List<dynamic>>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getFeeRate({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
|
|
@ -425,22 +425,6 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
|
|||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUnhashedUsedCoinsTags,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
required List<String>? sparkCoinHashes,
|
||||
|
@ -468,6 +452,49 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
|
|||
returnValue: _i6.Future<int>.value(0),
|
||||
) as _i6.Future<int>);
|
||||
@override
|
||||
_i6.Future<Set<String>> getMempoolTxids({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMempoolTxids,
|
||||
[],
|
||||
{#requestID: requestID},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getMempoolSparkData({
|
||||
String? requestID,
|
||||
required List<String>? txids,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMempoolSparkData,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#txids: txids,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<List<List<dynamic>>> getSparkUnhashedUsedCoinsTagsWithTxHashes({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUnhashedUsedCoinsTagsWithTxHashes,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<List<List<dynamic>>>.value(<List<dynamic>>[]),
|
||||
) as _i6.Future<List<List<dynamic>>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getFeeRate({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
|
|
@ -425,22 +425,6 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
|
|||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUnhashedUsedCoinsTags,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
required List<String>? sparkCoinHashes,
|
||||
|
@ -468,6 +452,49 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
|
|||
returnValue: _i6.Future<int>.value(0),
|
||||
) as _i6.Future<int>);
|
||||
@override
|
||||
_i6.Future<Set<String>> getMempoolTxids({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMempoolTxids,
|
||||
[],
|
||||
{#requestID: requestID},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getMempoolSparkData({
|
||||
String? requestID,
|
||||
required List<String>? txids,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getMempoolSparkData,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#txids: txids,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<List<List<dynamic>>> getSparkUnhashedUsedCoinsTagsWithTxHashes({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUnhashedUsedCoinsTagsWithTxHashes,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<List<List<dynamic>>>.value(<List<dynamic>>[]),
|
||||
) as _i6.Future<List<List<dynamic>>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getFeeRate({String? requestID}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
|
2068
test/sixteen_word_list_sanity_test.dart
Normal file
2068
test/sixteen_word_list_sanity_test.dart
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue