Merge pull request #887 from cypherstack/testing

Various fixes and tweaks
This commit is contained in:
julian-CStack 2024-06-12 19:04:14 -06:00 committed by GitHub
commit cb4a9b5180
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 5253 additions and 391 deletions

View file

@ -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,

View file

@ -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 {

View file

@ -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);
}
}

View 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;
}
}

View file

@ -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
);
""",
);

View file

@ -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 {

View file

@ -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,

View file

@ -100,7 +100,7 @@ class _FiroCacheWorker {
case FCFuncName._updateSparkUsedTagsWith:
result = _updateSparkUsedTagsWith(
usedTagsCacheDb,
task.data as List<String>,
task.data as List<List<dynamic>>,
);
break;
}

View file

@ -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],
);
}

View file

@ -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.

View file

@ -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

View file

@ -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) {

View file

@ -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,
),

View file

@ -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(

View 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),
),
),
],
),
);
}
}

View file

@ -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()) {

View file

@ -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",

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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 {

View file

@ -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

View file

@ -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),

View file

@ -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);

View file

@ -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(),
},
);
}

View file

@ -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,

View file

@ -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

View file

@ -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;
}
// }

View file

@ -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 (_) {
//

View file

@ -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(

View file

@ -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(

View file

@ -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(

View file

@ -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(

View file

@ -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(

View file

@ -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(

File diff suppressed because it is too large Load diff