mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-03-12 09:27:01 +00:00
Merge branch 'firo_cache_refactor' into campfire
This commit is contained in:
commit
5fa6aa9328
62 changed files with 1590 additions and 754 deletions
|
@ -1 +1 @@
|
|||
Subproject commit 81a4f74ea068d3d1026c8e564ee9b0b28cee20c4
|
||||
Subproject commit 4b87151d4914606b911f738a8236a6e54a6d8ecb
|
|
@ -83,6 +83,21 @@ PODS:
|
|||
- SDWebImage/Core (5.13.2)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- sqlite3 (3.46.0):
|
||||
- sqlite3/common (= 3.46.0)
|
||||
- sqlite3/common (3.46.0)
|
||||
- sqlite3/fts5 (3.46.0):
|
||||
- sqlite3/common
|
||||
- sqlite3/perf-threadsafe (3.46.0):
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.46.0):
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- sqlite3 (~> 3.46.0)
|
||||
- sqlite3/fts5
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
- stack_wallet_backup (0.0.1):
|
||||
- Flutter
|
||||
- SwiftProtobuf (1.19.0)
|
||||
|
@ -117,6 +132,7 @@ DEPENDENCIES:
|
|||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
|
||||
- stack_wallet_backup (from `.symlinks/plugins/stack_wallet_backup/ios`)
|
||||
- tor_ffi_plugin (from `.symlinks/plugins/tor_ffi_plugin/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
@ -129,6 +145,7 @@ SPEC REPOS:
|
|||
- MTBBarcodeScanner
|
||||
- ReachabilitySwift
|
||||
- SDWebImage
|
||||
- sqlite3
|
||||
- SwiftProtobuf
|
||||
- SwiftyGif
|
||||
|
||||
|
@ -177,6 +194,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
sqlite3_flutter_libs:
|
||||
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
|
||||
stack_wallet_backup:
|
||||
:path: ".symlinks/plugins/stack_wallet_backup/ios"
|
||||
tor_ffi_plugin:
|
||||
|
@ -203,7 +222,7 @@ SPEC CHECKSUMS:
|
|||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||
frostdart: 4c72b69ccac2f13ede744107db046a125acce597
|
||||
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
|
||||
integration_test: 13825b8a9334a850581300559b8839134b124670
|
||||
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
||||
lelantus: 417f0221260013dfc052cae9cf4b741b6479edba
|
||||
local_auth: 1740f55d7af0a2e2a8684ce225fe79d8931e808c
|
||||
|
@ -214,6 +233,8 @@ SPEC CHECKSUMS:
|
|||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
|
||||
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
||||
sqlite3: 154b084339ede06960a5b3c8160066adc9176b7d
|
||||
sqlite3_flutter_libs: 0d611efdf6d1c9297d5ab03dab21b75aeebdae31
|
||||
stack_wallet_backup: 5b8563aba5d8ffbf2ce1944331ff7294a0ec7c03
|
||||
SwiftProtobuf: 6ef3f0e422ef90d6605ca20b21a94f6c1324d6b3
|
||||
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
||||
|
|
|
@ -422,6 +422,20 @@ class DbVersionMigrator with WalletDB {
|
|||
// try to continue migrating
|
||||
return await migrate(12, secureStore: secureStore);
|
||||
|
||||
case 12:
|
||||
// migrate
|
||||
await _v12(secureStore);
|
||||
|
||||
// update version
|
||||
await DB.instance.put<dynamic>(
|
||||
boxName: DB.boxNameDBInfo,
|
||||
key: "hive_data_version",
|
||||
value: 13,
|
||||
);
|
||||
|
||||
// try to continue migrating
|
||||
return await migrate(13, secureStore: secureStore);
|
||||
|
||||
default:
|
||||
// finally return
|
||||
return;
|
||||
|
@ -701,4 +715,15 @@ class DbVersionMigrator with WalletDB {
|
|||
Future<void> _v11(SecureStorageInterface secureStore) async {
|
||||
await migrateWalletsToIsar(secureStore: secureStore);
|
||||
}
|
||||
|
||||
Future<void> _v12(SecureStorageInterface secureStore) async {
|
||||
for (final identifier in ["firo", "firoTestNet"]) {
|
||||
await DB.instance.deleteBoxFromDisk(
|
||||
boxName: "${identifier}_anonymitySetSparkCache",
|
||||
);
|
||||
await DB.instance.deleteBoxFromDisk(
|
||||
boxName: "${identifier}_sparkUsedCoinsTagsCache",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'dart:isolate';
|
|||
import 'package:cw_core/wallet_info.dart' as xmr;
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
|
||||
import '../../app_config.dart';
|
||||
import '../../models/exchange/response_objects/trade.dart';
|
||||
import '../../models/node_model.dart';
|
||||
|
@ -55,12 +56,8 @@ class DB {
|
|||
// firo only
|
||||
String _boxNameSetCache({required CryptoCurrency currency}) =>
|
||||
"${currency.identifier}_anonymitySetCache";
|
||||
String _boxNameSetSparkCache({required CryptoCurrency currency}) =>
|
||||
"${currency.identifier}_anonymitySetSparkCache";
|
||||
String _boxNameUsedSerialsCache({required CryptoCurrency currency}) =>
|
||||
"${currency.identifier}_usedSerialsCache";
|
||||
String _boxNameSparkUsedCoinsTagsCache({required CryptoCurrency currency}) =>
|
||||
"${currency.identifier}_sparkUsedCoinsTagsCache";
|
||||
|
||||
Box<NodeModel>? _boxNodeModels;
|
||||
Box<NodeModel>? _boxPrimaryNodes;
|
||||
|
@ -81,7 +78,6 @@ class DB {
|
|||
|
||||
final Map<String, Box<dynamic>> _txCacheBoxes = {};
|
||||
final Map<String, Box<dynamic>> _setCacheBoxes = {};
|
||||
final Map<String, Box<dynamic>> _setSparkCacheBoxes = {};
|
||||
final Map<String, Box<dynamic>> _usedSerialsCacheBoxes = {};
|
||||
final Map<String, Box<dynamic>> _getSparkUsedCoinsTagsCacheBoxes = {};
|
||||
|
||||
|
@ -213,16 +209,6 @@ class DB {
|
|||
await Hive.openBox<dynamic>(_boxNameSetCache(currency: currency));
|
||||
}
|
||||
|
||||
Future<Box<dynamic>> getSparkAnonymitySetCacheBox({
|
||||
required CryptoCurrency currency,
|
||||
}) async {
|
||||
if (_setSparkCacheBoxes[currency.identifier]?.isOpen != true) {
|
||||
_setSparkCacheBoxes.remove(currency.identifier);
|
||||
}
|
||||
return _setSparkCacheBoxes[currency.identifier] ??=
|
||||
await Hive.openBox<dynamic>(_boxNameSetSparkCache(currency: currency));
|
||||
}
|
||||
|
||||
Future<void> closeAnonymitySetCacheBox({
|
||||
required CryptoCurrency currency,
|
||||
}) async {
|
||||
|
@ -241,18 +227,6 @@ class DB {
|
|||
);
|
||||
}
|
||||
|
||||
Future<Box<dynamic>> getSparkUsedCoinsTagsCacheBox({
|
||||
required CryptoCurrency currency,
|
||||
}) async {
|
||||
if (_getSparkUsedCoinsTagsCacheBoxes[currency.identifier]?.isOpen != true) {
|
||||
_getSparkUsedCoinsTagsCacheBoxes.remove(currency.identifier);
|
||||
}
|
||||
return _getSparkUsedCoinsTagsCacheBoxes[currency.identifier] ??=
|
||||
await Hive.openBox<dynamic>(
|
||||
_boxNameSparkUsedCoinsTagsCache(currency: currency),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> closeUsedSerialsCacheBox({
|
||||
required CryptoCurrency currency,
|
||||
}) async {
|
||||
|
@ -266,15 +240,9 @@ class DB {
|
|||
await deleteAll<dynamic>(boxName: _boxNameTxCache(currency: currency));
|
||||
if (currency is Firo) {
|
||||
await deleteAll<dynamic>(boxName: _boxNameSetCache(currency: currency));
|
||||
await deleteAll<dynamic>(
|
||||
boxName: _boxNameSetSparkCache(currency: currency),
|
||||
);
|
||||
await deleteAll<dynamic>(
|
||||
boxName: _boxNameUsedSerialsCache(currency: currency),
|
||||
);
|
||||
await deleteAll<dynamic>(
|
||||
boxName: _boxNameSparkUsedCoinsTagsCache(currency: currency),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
122
lib/db/sqlite/firo_cache.dart
Normal file
122
lib/db/sqlite/firo_cache.dart
Normal file
|
@ -0,0 +1,122 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:sqlite3/sqlite3.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../../electrumx_rpc/electrumx_client.dart';
|
||||
import '../../utilities/extensions/extensions.dart';
|
||||
import '../../utilities/logger.dart';
|
||||
import '../../utilities/stack_file_system.dart';
|
||||
|
||||
part 'firo_cache_coordinator.dart';
|
||||
part 'firo_cache_reader.dart';
|
||||
part 'firo_cache_writer.dart';
|
||||
part 'firo_cache_worker.dart';
|
||||
|
||||
/// Temporary debugging log function for this file
|
||||
void _debugLog(Object? object) {
|
||||
if (kDebugMode) {
|
||||
Logging.instance.log(
|
||||
object,
|
||||
level: LogLevel.Debug,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _FiroCache {
|
||||
static const String sqliteDbFileName = "firo_ex_cache.sqlite3";
|
||||
|
||||
static Database? _db;
|
||||
static Database get db {
|
||||
if (_db == null) {
|
||||
throw Exception(
|
||||
"FiroCache.init() must be called before accessing FiroCache.db!",
|
||||
);
|
||||
}
|
||||
return _db!;
|
||||
}
|
||||
|
||||
static Future<void>? _initFuture;
|
||||
static Future<void> init() => _initFuture ??= _init();
|
||||
|
||||
static Future<void> _init() async {
|
||||
final sqliteDir = await StackFileSystem.applicationSQLiteDirectory();
|
||||
|
||||
final file = File("${sqliteDir.path}/$sqliteDbFileName");
|
||||
|
||||
final exists = await file.exists();
|
||||
if (!exists) {
|
||||
await _createDb(file.path);
|
||||
}
|
||||
|
||||
_db = sqlite3.open(
|
||||
file.path,
|
||||
mode: OpenMode.readWrite,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> _deleteAllCache() async {
|
||||
final start = DateTime.now();
|
||||
db.execute(
|
||||
"""
|
||||
DELETE FROM SparkSet;
|
||||
DELETE FROM SparkCoin;
|
||||
DELETE FROM SparkSetCoins;
|
||||
DELETE FROM SparkUsedCoinTags;
|
||||
VACUUM;
|
||||
""",
|
||||
);
|
||||
_debugLog(
|
||||
"_deleteAllCache() "
|
||||
"duration = ${DateTime.now().difference(start)}",
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> _createDb(String file) async {
|
||||
final db = sqlite3.open(
|
||||
file,
|
||||
mode: OpenMode.readWriteCreate,
|
||||
);
|
||||
|
||||
db.execute(
|
||||
"""
|
||||
CREATE TABLE SparkSet (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
blockHash TEXT NOT NULL,
|
||||
setHash TEXT NOT NULL,
|
||||
groupId INTEGER NOT NULL,
|
||||
timestampUTC INTEGER NOT NULL,
|
||||
UNIQUE (blockHash, setHash, groupId)
|
||||
);
|
||||
|
||||
CREATE TABLE SparkCoin (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
serialized TEXT NOT NULL,
|
||||
txHash TEXT NOT NULL,
|
||||
context TEXT NOT NULL,
|
||||
UNIQUE(serialized, txHash, context)
|
||||
);
|
||||
|
||||
CREATE TABLE SparkSetCoins (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
setId INTEGER NOT NULL,
|
||||
coinId INTEGER NOT NULL,
|
||||
FOREIGN KEY (setId) REFERENCES SparkSet(id),
|
||||
FOREIGN KEY (coinId) REFERENCES SparkCoin(id)
|
||||
);
|
||||
|
||||
CREATE TABLE SparkUsedCoinTags (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
tag TEXT NOT NULL UNIQUE
|
||||
);
|
||||
""",
|
||||
);
|
||||
|
||||
db.dispose();
|
||||
}
|
||||
}
|
162
lib/db/sqlite/firo_cache_coordinator.dart
Normal file
162
lib/db/sqlite/firo_cache_coordinator.dart
Normal file
|
@ -0,0 +1,162 @@
|
|||
part of 'firo_cache.dart';
|
||||
|
||||
/// Wrapper class for [_FiroCache] as [_FiroCache] should eventually be handled in a
|
||||
/// background isolate and [FiroCacheCoordinator] should manage that isolate
|
||||
abstract class FiroCacheCoordinator {
|
||||
static _FiroCacheWorker? _worker;
|
||||
|
||||
static bool _init = false;
|
||||
static Future<void> init() async {
|
||||
if (_init) {
|
||||
return;
|
||||
}
|
||||
_init = true;
|
||||
await _FiroCache.init();
|
||||
_worker = await _FiroCacheWorker.spawn();
|
||||
}
|
||||
|
||||
static Future<void> clearSharedCache() async {
|
||||
return await _FiroCache._deleteAllCache();
|
||||
}
|
||||
|
||||
static Future<String> getSparkCacheSize() async {
|
||||
final dir = await StackFileSystem.applicationSQLiteDirectory();
|
||||
final cacheFile = File("${dir.path}/${_FiroCache.sqliteDbFileName}");
|
||||
final int bytes;
|
||||
if (await cacheFile.exists()) {
|
||||
bytes = await cacheFile.length();
|
||||
} else {
|
||||
bytes = 0;
|
||||
}
|
||||
|
||||
if (bytes < 1024) {
|
||||
return '$bytes B';
|
||||
} else if (bytes < 1048576) {
|
||||
final double kbSize = bytes / 1024;
|
||||
return '${kbSize.toStringAsFixed(2)} KB';
|
||||
} else if (bytes < 1073741824) {
|
||||
final double mbSize = bytes / 1048576;
|
||||
return '${mbSize.toStringAsFixed(2)} MB';
|
||||
} else {
|
||||
final double gbSize = bytes / 1073741824;
|
||||
return '${gbSize.toStringAsFixed(2)} GB';
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> runFetchAndUpdateSparkUsedCoinTags(
|
||||
ElectrumXClient client,
|
||||
) async {
|
||||
final count = await FiroCacheCoordinator.getUsedCoinTagsLastAddedRowId();
|
||||
final unhashedTags = await client.getSparkUnhashedUsedCoinsTags(
|
||||
startNumber: count,
|
||||
);
|
||||
if (unhashedTags.isNotEmpty) {
|
||||
await _worker!.runTask(
|
||||
FCTask(
|
||||
func: FCFuncName._updateSparkUsedTagsWith,
|
||||
data: unhashedTags,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> runFetchAndUpdateSparkAnonSetCacheForGroupId(
|
||||
int groupId,
|
||||
ElectrumXClient client,
|
||||
) async {
|
||||
final blockhashResult =
|
||||
await FiroCacheCoordinator.getLatestSetInfoForGroupId(
|
||||
groupId,
|
||||
);
|
||||
final blockHash = blockhashResult?.blockHash ?? "";
|
||||
|
||||
final json = await client.getSparkAnonymitySet(
|
||||
coinGroupId: groupId.toString(),
|
||||
startBlockHash: blockHash.toHexReversedFromBase64,
|
||||
);
|
||||
|
||||
await _worker!.runTask(
|
||||
FCTask(
|
||||
func: FCFuncName._updateSparkAnonSetCoinsWith,
|
||||
data: (groupId, json),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
|
||||
static Future<Set<String>> getUsedCoinTags(int startNumber) async {
|
||||
final result = await _Reader._getSparkUsedCoinTags(
|
||||
startNumber,
|
||||
db: _FiroCache.db,
|
||||
);
|
||||
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> getUsedCoinTagsLastAddedRowId() async {
|
||||
final result = await _Reader._getUsedCoinTagsLastAddedRowId(
|
||||
db: _FiroCache.db,
|
||||
);
|
||||
if (result.isEmpty) {
|
||||
return 0;
|
||||
}
|
||||
return result.first["highestId"] as int? ?? 0;
|
||||
}
|
||||
|
||||
static Future<bool> checkTagIsUsed(
|
||||
String tag,
|
||||
) async {
|
||||
return await _Reader._checkTagIsUsed(
|
||||
tag,
|
||||
db: _FiroCache.db,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<ResultSet> getSetCoinsForGroupId(
|
||||
int groupId, {
|
||||
int? newerThanTimeStamp,
|
||||
}) async {
|
||||
return await _Reader._getSetCoinsForGroupId(
|
||||
groupId,
|
||||
db: _FiroCache.db,
|
||||
newerThanTimeStamp: newerThanTimeStamp,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<
|
||||
({
|
||||
String blockHash,
|
||||
String setHash,
|
||||
int timestampUTC,
|
||||
})?> getLatestSetInfoForGroupId(
|
||||
int groupId,
|
||||
) async {
|
||||
final result = await _Reader._getLatestSetInfoForGroupId(
|
||||
groupId,
|
||||
db: _FiroCache.db,
|
||||
);
|
||||
|
||||
if (result.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
blockHash: result.first["blockHash"] as String,
|
||||
setHash: result.first["setHash"] as String,
|
||||
timestampUTC: result.first["timestampUTC"] as int,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<bool> checkSetInfoForGroupIdExists(
|
||||
int groupId,
|
||||
) async {
|
||||
return await _Reader._checkSetInfoForGroupIdExists(
|
||||
groupId,
|
||||
db: _FiroCache.db,
|
||||
);
|
||||
}
|
||||
}
|
103
lib/db/sqlite/firo_cache_reader.dart
Normal file
103
lib/db/sqlite/firo_cache_reader.dart
Normal file
|
@ -0,0 +1,103 @@
|
|||
part of 'firo_cache.dart';
|
||||
|
||||
/// Keep all fetch queries in this separate file
|
||||
abstract class _Reader {
|
||||
// ===========================================================================
|
||||
// =============== Spark anonymity set queries ===============================
|
||||
|
||||
static Future<ResultSet> _getSetCoinsForGroupId(
|
||||
int groupId, {
|
||||
required Database db,
|
||||
int? newerThanTimeStamp,
|
||||
}) async {
|
||||
String query = """
|
||||
SELECT sc.serialized, sc.txHash, sc.context
|
||||
FROM SparkSet AS ss
|
||||
JOIN SparkSetCoins AS ssc ON ss.id = ssc.setId
|
||||
JOIN SparkCoin AS sc ON ssc.coinId = sc.id
|
||||
WHERE ss.groupId = $groupId
|
||||
""";
|
||||
|
||||
if (newerThanTimeStamp != null) {
|
||||
query += " AND ss.timestampUTC"
|
||||
" > $newerThanTimeStamp";
|
||||
}
|
||||
|
||||
return db.select("$query;");
|
||||
}
|
||||
|
||||
static Future<ResultSet> _getLatestSetInfoForGroupId(
|
||||
int groupId, {
|
||||
required Database db,
|
||||
}) async {
|
||||
final query = """
|
||||
SELECT ss.blockHash, ss.setHash, ss.timestampUTC
|
||||
FROM SparkSet ss
|
||||
WHERE ss.groupId = $groupId
|
||||
ORDER BY ss.timestampUTC DESC
|
||||
LIMIT 1;
|
||||
""";
|
||||
|
||||
return db.select("$query;");
|
||||
}
|
||||
|
||||
static Future<bool> _checkSetInfoForGroupIdExists(
|
||||
int groupId, {
|
||||
required Database db,
|
||||
}) async {
|
||||
final query = """
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM SparkSet
|
||||
WHERE groupId = $groupId
|
||||
) AS setExists;
|
||||
""";
|
||||
|
||||
return db.select("$query;").first["setExists"] == 1;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// =============== Spark used coin tags queries ==============================
|
||||
|
||||
static Future<ResultSet> _getSparkUsedCoinTags(
|
||||
int startNumber, {
|
||||
required Database db,
|
||||
}) async {
|
||||
String query = """
|
||||
SELECT tag
|
||||
FROM SparkUsedCoinTags
|
||||
""";
|
||||
|
||||
if (startNumber > 0) {
|
||||
query += " WHERE id >= $startNumber";
|
||||
}
|
||||
|
||||
return db.select("$query;");
|
||||
}
|
||||
|
||||
static Future<ResultSet> _getUsedCoinTagsLastAddedRowId({
|
||||
required Database db,
|
||||
}) async {
|
||||
const query = """
|
||||
SELECT MAX(id) AS highestId
|
||||
FROM SparkUsedCoinTags;
|
||||
""";
|
||||
|
||||
return db.select("$query;");
|
||||
}
|
||||
|
||||
static Future<bool> _checkTagIsUsed(
|
||||
String tag, {
|
||||
required Database db,
|
||||
}) async {
|
||||
final query = """
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM SparkUsedCoinTags
|
||||
WHERE tag = '$tag'
|
||||
) AS tagExists;
|
||||
""";
|
||||
|
||||
return db.select("$query;").first["tagExists"] == 1;
|
||||
}
|
||||
}
|
120
lib/db/sqlite/firo_cache_worker.dart
Normal file
120
lib/db/sqlite/firo_cache_worker.dart
Normal file
|
@ -0,0 +1,120 @@
|
|||
part of 'firo_cache.dart';
|
||||
|
||||
enum FCFuncName {
|
||||
_updateSparkAnonSetCoinsWith,
|
||||
_updateSparkUsedTagsWith,
|
||||
}
|
||||
|
||||
class FCTask {
|
||||
final id = const Uuid().v4();
|
||||
final FCFuncName func;
|
||||
final dynamic data;
|
||||
|
||||
FCTask({required this.func, required this.data});
|
||||
}
|
||||
|
||||
class _FiroCacheWorker {
|
||||
final SendPort _commands;
|
||||
final ReceivePort _responses;
|
||||
final Map<String, Completer<Object?>> _activeRequests = {};
|
||||
|
||||
Future<Object?> runTask(FCTask task) async {
|
||||
final completer = Completer<Object?>.sync();
|
||||
_activeRequests[task.id] = completer;
|
||||
_commands.send(task);
|
||||
return await completer.future;
|
||||
}
|
||||
|
||||
static Future<_FiroCacheWorker> spawn() async {
|
||||
final sqliteDir = await StackFileSystem.applicationSQLiteDirectory();
|
||||
final dbFilePath = "${sqliteDir.path}/${_FiroCache.sqliteDbFileName}";
|
||||
|
||||
final initPort = RawReceivePort();
|
||||
final connection = Completer<(ReceivePort, SendPort)>.sync();
|
||||
|
||||
initPort.handler = (dynamic initialMessage) {
|
||||
final commandPort = initialMessage as SendPort;
|
||||
connection.complete(
|
||||
(
|
||||
ReceivePort.fromRawReceivePort(initPort),
|
||||
commandPort,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
await Isolate.spawn(
|
||||
_startWorkerIsolate,
|
||||
(initPort.sendPort, dbFilePath),
|
||||
);
|
||||
} catch (_) {
|
||||
initPort.close();
|
||||
rethrow;
|
||||
}
|
||||
|
||||
final (receivePort, sendPort) = await connection.future;
|
||||
|
||||
return _FiroCacheWorker._(receivePort, sendPort);
|
||||
}
|
||||
|
||||
_FiroCacheWorker._(this._responses, this._commands) {
|
||||
_responses.listen(_handleResponsesFromIsolate);
|
||||
}
|
||||
|
||||
void _handleResponsesFromIsolate(dynamic message) {
|
||||
final (id, error) = message as (String, Object?);
|
||||
final completer = _activeRequests.remove(id)!;
|
||||
|
||||
if (error != null) {
|
||||
completer.completeError(error);
|
||||
} else {
|
||||
completer.complete(id);
|
||||
}
|
||||
}
|
||||
|
||||
static void _handleCommandsToIsolate(
|
||||
ReceivePort receivePort,
|
||||
SendPort sendPort,
|
||||
Database db,
|
||||
Mutex mutex,
|
||||
) {
|
||||
receivePort.listen((message) {
|
||||
final task = message as FCTask;
|
||||
|
||||
mutex.protect(() async {
|
||||
try {
|
||||
final FCResult result;
|
||||
switch (task.func) {
|
||||
case FCFuncName._updateSparkAnonSetCoinsWith:
|
||||
final data = task.data as (int, Map<String, dynamic>);
|
||||
result = _updateSparkAnonSetCoinsWith(db, data.$2, data.$1);
|
||||
break;
|
||||
|
||||
case FCFuncName._updateSparkUsedTagsWith:
|
||||
result = _updateSparkUsedTagsWith(db, task.data as List<String>);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
sendPort.send((task.id, null));
|
||||
} else {
|
||||
sendPort.send((task.id, result.error!));
|
||||
}
|
||||
} catch (e) {
|
||||
sendPort.send((task.id, e));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static void _startWorkerIsolate((SendPort, String) args) {
|
||||
final receivePort = ReceivePort();
|
||||
args.$1.send(receivePort.sendPort);
|
||||
final mutex = Mutex();
|
||||
final db = sqlite3.open(
|
||||
args.$2,
|
||||
mode: OpenMode.readWrite,
|
||||
);
|
||||
_handleCommandsToIsolate(receivePort, args.$1, db, mutex);
|
||||
}
|
||||
}
|
169
lib/db/sqlite/firo_cache_writer.dart
Normal file
169
lib/db/sqlite/firo_cache_writer.dart
Normal file
|
@ -0,0 +1,169 @@
|
|||
part of 'firo_cache.dart';
|
||||
|
||||
class FCResult {
|
||||
final bool success;
|
||||
final Object? error;
|
||||
|
||||
FCResult({required this.success, this.error});
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// ================== write to spark used tags cache =========================
|
||||
|
||||
/// update the sqlite cache
|
||||
/// Expected json format:
|
||||
/// returns true if successful, otherwise some exception
|
||||
FCResult _updateSparkUsedTagsWith(
|
||||
Database db,
|
||||
List<String> tags,
|
||||
) {
|
||||
// hash the tags here since this function is called in a background isolate
|
||||
final hashedTags = LibSpark.hashTags(base64Tags: tags);
|
||||
|
||||
if (hashedTags.isEmpty) {
|
||||
// nothing to add, return early
|
||||
return FCResult(success: true);
|
||||
}
|
||||
|
||||
db.execute("BEGIN;");
|
||||
try {
|
||||
for (final tag in hashedTags) {
|
||||
db.execute(
|
||||
"""
|
||||
INSERT OR IGNORE INTO SparkUsedCoinTags (tag)
|
||||
VALUES (?);
|
||||
""",
|
||||
[tag],
|
||||
);
|
||||
}
|
||||
|
||||
db.execute("COMMIT;");
|
||||
|
||||
return FCResult(success: true);
|
||||
} catch (e) {
|
||||
db.execute("ROLLBACK;");
|
||||
return FCResult(success: false, error: e);
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// ================== write to spark anon set cache ==========================
|
||||
|
||||
/// update the sqlite cache
|
||||
/// Expected json format:
|
||||
/// {
|
||||
/// "blockHash": "someBlockHash",
|
||||
/// "setHash": "someSetHash",
|
||||
/// "coins": [
|
||||
/// ["serliazed1", "hash1", "context1"],
|
||||
/// ["serliazed2", "hash2", "context2"],
|
||||
/// ...
|
||||
/// ["serliazed3", "hash3", "context3"],
|
||||
/// ["serliazed4", "hash4", "context4"],
|
||||
/// ],
|
||||
/// }
|
||||
///
|
||||
/// returns true if successful, otherwise false
|
||||
FCResult _updateSparkAnonSetCoinsWith(
|
||||
Database db,
|
||||
Map<String, dynamic> json,
|
||||
int groupId,
|
||||
) {
|
||||
final blockHash = json["blockHash"] as String;
|
||||
final setHash = json["setHash"] as String;
|
||||
final coinsRaw = json["coins"] as List;
|
||||
|
||||
if (coinsRaw.isEmpty) {
|
||||
// no coins to actually insert
|
||||
return FCResult(success: true);
|
||||
}
|
||||
|
||||
final checkResult = db.select(
|
||||
"""
|
||||
SELECT *
|
||||
FROM SparkSet
|
||||
WHERE blockHash = ? AND setHash = ? AND groupId = ?;
|
||||
""",
|
||||
[
|
||||
blockHash,
|
||||
setHash,
|
||||
groupId,
|
||||
],
|
||||
);
|
||||
|
||||
if (checkResult.isNotEmpty) {
|
||||
// already up to date
|
||||
return FCResult(success: true);
|
||||
}
|
||||
|
||||
final coins = coinsRaw
|
||||
.map(
|
||||
(e) => [
|
||||
e[0] as String,
|
||||
e[1] as String,
|
||||
e[2] as String,
|
||||
],
|
||||
)
|
||||
.toList();
|
||||
|
||||
final timestamp = DateTime.now().toUtc().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
db.execute("BEGIN;");
|
||||
try {
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO SparkSet (blockHash, setHash, groupId, timestampUTC)
|
||||
VALUES (?, ?, ?, ?);
|
||||
""",
|
||||
[blockHash, setHash, groupId, timestamp],
|
||||
);
|
||||
final setId = db.lastInsertRowId;
|
||||
|
||||
for (final coin in coins) {
|
||||
int coinId;
|
||||
try {
|
||||
// try to insert and get row id
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO SparkCoin (serialized, txHash, context)
|
||||
VALUES (?, ?, ?);
|
||||
""",
|
||||
coin,
|
||||
);
|
||||
coinId = db.lastInsertRowId;
|
||||
} on SqliteException catch (e) {
|
||||
// if there already is a matching coin in the db
|
||||
// just grab its row id
|
||||
if (e.extendedResultCode == 2067) {
|
||||
final result = db.select(
|
||||
"""
|
||||
SELECT id
|
||||
FROM SparkCoin
|
||||
WHERE serialized = ? AND txHash = ? AND context = ?;
|
||||
""",
|
||||
coin,
|
||||
);
|
||||
coinId = result.first["id"] as int;
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// finally add the row id to the newly added set
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO SparkSetCoins (setId, coinId)
|
||||
VALUES (?, ?);
|
||||
""",
|
||||
[setId, coinId],
|
||||
);
|
||||
}
|
||||
|
||||
db.execute("COMMIT;");
|
||||
|
||||
return FCResult(success: true);
|
||||
} catch (e) {
|
||||
db.execute("ROLLBACK;");
|
||||
return FCResult(success: false, error: e);
|
||||
}
|
||||
}
|
|
@ -116,70 +116,6 @@ class CachedElectrumXClient {
|
|||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getSparkAnonymitySet({
|
||||
required String groupId,
|
||||
String blockhash = "",
|
||||
required CryptoCurrency cryptoCurrency,
|
||||
required bool useOnlyCacheIfNotEmpty,
|
||||
}) async {
|
||||
try {
|
||||
final box = await DB.instance.getSparkAnonymitySetCacheBox(
|
||||
currency: cryptoCurrency,
|
||||
);
|
||||
final cachedSet = box.get(groupId) as Map?;
|
||||
|
||||
Map<String, dynamic> set;
|
||||
|
||||
// null check to see if there is a cached set
|
||||
if (cachedSet == null) {
|
||||
set = {
|
||||
"coinGroupID": int.parse(groupId),
|
||||
"blockHash": blockhash,
|
||||
"setHash": "",
|
||||
"coins": <dynamic>[],
|
||||
};
|
||||
} else {
|
||||
set = Map<String, dynamic>.from(cachedSet);
|
||||
if (useOnlyCacheIfNotEmpty) {
|
||||
return set;
|
||||
}
|
||||
}
|
||||
|
||||
final newSet = await electrumXClient.getSparkAnonymitySet(
|
||||
coinGroupId: groupId,
|
||||
startBlockHash: set["blockHash"] as String,
|
||||
);
|
||||
|
||||
// update set with new data
|
||||
if (newSet["setHash"] != "" && set["setHash"] != newSet["setHash"]) {
|
||||
set["setHash"] = newSet["setHash"];
|
||||
set["blockHash"] = newSet["blockHash"];
|
||||
for (int i = (newSet["coins"] as List).length - 1; i >= 0; i--) {
|
||||
// TODO verify this is correct (or append?)
|
||||
if ((set["coins"] as List)
|
||||
.where((e) => e[0] == newSet["coins"][i][0])
|
||||
.isEmpty) {
|
||||
set["coins"].insert(0, newSet["coins"][i]);
|
||||
}
|
||||
}
|
||||
// save set to db
|
||||
await box.put(groupId, set);
|
||||
Logging.instance.log(
|
||||
"Updated current anonymity set for ${cryptoCurrency.identifier} with group ID $groupId",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
}
|
||||
|
||||
return set;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to process CachedElectrumX.getSparkAnonymitySet(): $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
String base64ToHex(String source) =>
|
||||
base64Decode(LineSplitter.split(source).join())
|
||||
.map((e) => e.toRadixString(16).padLeft(2, '0'))
|
||||
|
@ -284,53 +220,6 @@ class CachedElectrumXClient {
|
|||
}
|
||||
}
|
||||
|
||||
Future<Set<String>> getSparkUsedCoinsTags({
|
||||
required CryptoCurrency cryptoCurrency,
|
||||
}) async {
|
||||
try {
|
||||
final box = await DB.instance.getSparkUsedCoinsTagsCacheBox(
|
||||
currency: cryptoCurrency,
|
||||
);
|
||||
|
||||
final _list = box.get("tags") as List?;
|
||||
|
||||
final Set<String> cachedTags =
|
||||
_list == null ? {} : List<String>.from(_list).toSet();
|
||||
|
||||
final startNumber = max(
|
||||
0,
|
||||
cachedTags.length - 100, // 100 being some arbitrary buffer
|
||||
);
|
||||
|
||||
final newTags = await electrumXClient.getSparkUsedCoinsTags(
|
||||
startNumber: startNumber,
|
||||
);
|
||||
|
||||
// ensure we are getting some overlap so we know we are not missing any
|
||||
if (cachedTags.isNotEmpty && newTags.isNotEmpty) {
|
||||
assert(cachedTags.intersection(newTags).isNotEmpty);
|
||||
}
|
||||
|
||||
// Make newTags an Iterable<String>.
|
||||
final Iterable<String> iterableTags = newTags.map((e) => e.toString());
|
||||
|
||||
cachedTags.addAll(iterableTags);
|
||||
|
||||
await box.put(
|
||||
"tags",
|
||||
cachedTags.toList(),
|
||||
);
|
||||
|
||||
return cachedTags;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to process CachedElectrumX.getSparkUsedCoinsTags(): $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all cached transactions for the specified coin
|
||||
Future<void> clearSharedTransactionCache({
|
||||
required CryptoCurrency cryptoCurrency,
|
||||
|
|
|
@ -17,10 +17,9 @@ import 'package:electrum_adapter/electrum_adapter.dart' as electrum_adapter;
|
|||
import 'package:electrum_adapter/electrum_adapter.dart';
|
||||
import 'package:electrum_adapter/methods/specific/firo.dart';
|
||||
import 'package:event_bus/event_bus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'client_manager.dart';
|
||||
import 'package:stream_channel/stream_channel.dart';
|
||||
|
||||
import '../exceptions/electrumx/no_such_transaction.dart';
|
||||
import '../services/event_bus/events/global/tor_connection_status_changed_event.dart';
|
||||
import '../services/event_bus/events/global/tor_status_changed_event.dart';
|
||||
|
@ -29,7 +28,7 @@ import '../services/tor_service.dart';
|
|||
import '../utilities/logger.dart';
|
||||
import '../utilities/prefs.dart';
|
||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'package:stream_channel/stream_channel.dart';
|
||||
import 'client_manager.dart';
|
||||
|
||||
class WifiOnlyException implements Exception {}
|
||||
|
||||
|
@ -910,10 +909,7 @@ class ElectrumXClient {
|
|||
String? requestID,
|
||||
}) async {
|
||||
try {
|
||||
Logging.instance.log(
|
||||
"attempting to fetch spark.getsparkanonymityset...",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
final start = DateTime.now();
|
||||
await _checkElectrumAdapter();
|
||||
final Map<String, dynamic> response =
|
||||
await (getElectrumAdapter() as FiroElectrumClient)
|
||||
|
@ -922,7 +918,10 @@ class ElectrumXClient {
|
|||
startBlockHash: startBlockHash,
|
||||
);
|
||||
Logging.instance.log(
|
||||
"Fetching spark.getsparkanonymityset finished",
|
||||
"Finished ElectrumXClient.getSparkAnonymitySet(coinGroupId"
|
||||
"=$coinGroupId, startBlockHash=$startBlockHash). "
|
||||
"coins.length: ${(response["coins"] as List?)?.length}"
|
||||
"Duration=${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
return response;
|
||||
|
@ -933,16 +932,12 @@ class ElectrumXClient {
|
|||
|
||||
/// Takes [startNumber], if it is 0, we get the full set,
|
||||
/// otherwise the used tags after that number
|
||||
Future<Set<String>> getSparkUsedCoinsTags({
|
||||
Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int startNumber,
|
||||
}) async {
|
||||
try {
|
||||
// Use electrum_adapter package's getSparkUsedCoinsTags method.
|
||||
Logging.instance.log(
|
||||
"attempting to fetch spark.getusedcoinstags...",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
final start = DateTime.now();
|
||||
await _checkElectrumAdapter();
|
||||
final Map<String, dynamic> response =
|
||||
await (getElectrumAdapter() as FiroElectrumClient)
|
||||
|
@ -954,8 +949,16 @@ class ElectrumXClient {
|
|||
level: LogLevel.Info,
|
||||
);
|
||||
final map = Map<String, dynamic>.from(response);
|
||||
final set = Set<String>.from(map["tags"] as List);
|
||||
return await compute(_ffiHashTagsComputeWrapper, set);
|
||||
final tags = List<String>.from(map["tags"] as List);
|
||||
|
||||
Logging.instance.log(
|
||||
"Finished ElectrumXClient.getSparkUnhashedUsedCoinsTags(startNumber"
|
||||
"=$startNumber). "
|
||||
"Duration=${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
return tags;
|
||||
} catch (e) {
|
||||
Logging.instance.log(e, level: LogLevel.Error);
|
||||
rethrow;
|
||||
|
@ -1092,7 +1095,3 @@ class ElectrumXClient {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> _ffiHashTagsComputeWrapper(Set<String> base64Tags) {
|
||||
return LibSpark.hashTags(base64Tags: base64Tags);
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import 'app_config.dart';
|
|||
import 'db/db_version_migration.dart';
|
||||
import 'db/hive/db.dart';
|
||||
import 'db/isar/main_db.dart';
|
||||
import 'db/sqlite/firo_cache.dart';
|
||||
import 'models/exchange/change_now/exchange_transaction.dart';
|
||||
import 'models/exchange/change_now/exchange_transaction_status.dart';
|
||||
import 'models/exchange/response_objects/trade.dart';
|
||||
|
@ -200,6 +201,7 @@ void main(List<String> args) async {
|
|||
}
|
||||
|
||||
await StackFileSystem.initThemesDir();
|
||||
await FiroCacheCoordinator.init();
|
||||
|
||||
// Desktop migrate handled elsewhere (currently desktop_login_view.dart)
|
||||
if (!Util.isDesktop) {
|
||||
|
|
|
@ -124,12 +124,14 @@ const _LoglogLevelEnumValueMap = {
|
|||
r'Warning': r'Warning',
|
||||
r'Error': r'Error',
|
||||
r'Fatal': r'Fatal',
|
||||
r'Debug': r'Debug',
|
||||
};
|
||||
const _LoglogLevelValueEnumMap = {
|
||||
r'Info': LogLevel.Info,
|
||||
r'Warning': LogLevel.Warning,
|
||||
r'Error': LogLevel.Error,
|
||||
r'Fatal': LogLevel.Fatal,
|
||||
r'Debug': LogLevel.Debug,
|
||||
};
|
||||
|
||||
Id _logGetId(Log object) {
|
||||
|
|
|
@ -20,6 +20,7 @@ import '../../../frost_route_generator.dart';
|
|||
import '../../../models/isar/models/isar_models.dart';
|
||||
import '../../../providers/frost_wallet/frost_wallet_providers.dart';
|
||||
import '../../../providers/providers.dart';
|
||||
import '../../../providers/wallet/public_private_balance_state_provider.dart';
|
||||
import '../../../themes/coin_icon_provider.dart';
|
||||
import '../../../themes/stack_colors.dart';
|
||||
import '../../../utilities/amount/amount.dart';
|
||||
|
@ -234,7 +235,10 @@ class _FrostSendViewState extends ConsumerState<FrostSendView> {
|
|||
prefsChangeNotifierProvider.select(
|
||||
(value) => value.enableCoinControl,
|
||||
),
|
||||
);
|
||||
) &&
|
||||
(coin is Firo
|
||||
? ref.watch(publicPrivateBalanceStateProvider) == FiroType.public
|
||||
: true);
|
||||
|
||||
return ConditionalParent(
|
||||
condition: !Util.isDesktop,
|
||||
|
|
|
@ -998,10 +998,15 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
prefsChangeNotifierProvider.select(
|
||||
(value) => value.enableCoinControl,
|
||||
),
|
||||
);
|
||||
) &&
|
||||
(coin is Firo
|
||||
? ref.watch(publicPrivateBalanceStateProvider) == FiroType.public
|
||||
: true);
|
||||
|
||||
if (isFiro) {
|
||||
ref.listen(publicPrivateBalanceStateProvider, (previous, next) {
|
||||
selectedUTXOs = {};
|
||||
|
||||
if (ref.read(pSendAmount) == null) {
|
||||
setState(() {
|
||||
_calculateFeesFuture = calculateFees(
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
import '../../../notifications/show_flush_bar.dart';
|
||||
import '../../../providers/global/debug_service_provider.dart';
|
||||
import '../../../providers/providers.dart';
|
||||
|
@ -284,28 +285,33 @@ class HiddenSettings extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
//
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"Do nothing",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// const SizedBox(
|
||||
// height: 12,
|
||||
// ),
|
||||
// Consumer(
|
||||
// builder: (_, ref, __) {
|
||||
// return GestureDetector(
|
||||
// onTap: () async {
|
||||
// await showLoading(
|
||||
// whileFuture: FiroCache.init(),
|
||||
// context: context,
|
||||
// rootNavigator: true,
|
||||
// message: "initializing firo cache",
|
||||
// );
|
||||
// },
|
||||
// child: RoundedWhiteContainer(
|
||||
// child: Text(
|
||||
// "init firo_cache",
|
||||
// style: STextStyles.button(context).copyWith(
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .accentColorDark,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -16,6 +16,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../../db/hive/db.dart';
|
||||
import '../../../db/sqlite/firo_cache.dart';
|
||||
import '../../../models/epicbox_config_model.dart';
|
||||
import '../../../notifications/show_flush_bar.dart';
|
||||
import '../../../providers/global/wallets_provider.dart';
|
||||
|
@ -413,7 +414,8 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
|
|||
),
|
||||
);
|
||||
|
||||
if (result == "OK" && mounted) {
|
||||
if (result == "OK" &&
|
||||
context.mounted) {
|
||||
await showLoading(
|
||||
whileFuture: Future.wait<void>(
|
||||
[
|
||||
|
@ -426,6 +428,9 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
|
|||
.clearSharedTransactionCache(
|
||||
currency: coin,
|
||||
),
|
||||
if (coin is Firo)
|
||||
FiroCacheCoordinator
|
||||
.clearSharedCache(),
|
||||
],
|
||||
),
|
||||
context: context,
|
||||
|
|
|
@ -8,18 +8,14 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../../providers/db/main_db_provider.dart';
|
||||
import '../../../../providers/global/wallets_provider.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
import '../../../../utilities/text_styles.dart';
|
||||
import '../../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../../wallets/isar/models/wallet_info.dart';
|
||||
import '../../../../wallets/wallet/wallet.dart';
|
||||
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../../../widgets/background.dart';
|
||||
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../../widgets/custom_buttons/draggable_switch_button.dart';
|
||||
|
@ -40,46 +36,26 @@ class LelantusSettingsView extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _LelantusSettingsViewState extends ConsumerState<LelantusSettingsView> {
|
||||
late final TextEditingController _controller;
|
||||
late final String walletId;
|
||||
|
||||
final _focusNode = FocusNode();
|
||||
|
||||
bool _isInitialized = false;
|
||||
Wallet<CryptoCurrency>? wallet;
|
||||
bool _enableLelantusScanning = false;
|
||||
bool _isUpdatingLelantusScanning = false;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (!_isInitialized) {
|
||||
// Get the wallet.
|
||||
wallet = ref.watch(
|
||||
pWallets.select(
|
||||
(value) => value.getWallet(widget.walletId),
|
||||
),
|
||||
Future<void> _switchToggled(bool newValue) async {
|
||||
if (_isUpdatingLelantusScanning) return;
|
||||
_isUpdatingLelantusScanning = true; // Lock mutex.
|
||||
|
||||
try {
|
||||
// Toggle enableLelantusScanning in wallet info.
|
||||
await ref.read(pWalletInfo(widget.walletId)).updateOtherData(
|
||||
newEntries: {
|
||||
WalletInfoKeys.enableLelantusScanning: newValue,
|
||||
},
|
||||
isar: ref.read(mainDBProvider).isar,
|
||||
);
|
||||
|
||||
// Parse otherDataJsonString to get the enableLelantusScanning value.
|
||||
if (wallet?.info.otherDataJsonString != null) {
|
||||
final otherDataJson = json.decode(wallet!.info.otherDataJsonString!);
|
||||
_enableLelantusScanning =
|
||||
otherDataJson[WalletInfoKeys.enableLelantusScanning] as bool? ??
|
||||
false;
|
||||
}
|
||||
|
||||
_isInitialized = true; // Ensure this logic runs only once
|
||||
} finally {
|
||||
// ensure _isUpdatingLelantusScanning is set to false no matter what
|
||||
_isUpdatingLelantusScanning = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
|
@ -107,25 +83,12 @@ class _LelantusSettingsViewState extends ConsumerState<LelantusSettingsView> {
|
|||
height: 20,
|
||||
width: 40,
|
||||
child: DraggableSwitchButton(
|
||||
isOn: _enableLelantusScanning,
|
||||
onValueChanged: (newValue) async {
|
||||
if (_isUpdatingLelantusScanning) return;
|
||||
_isUpdatingLelantusScanning = true; // Lock mutex.
|
||||
|
||||
// Toggle enableLelantusScanning in wallet info.
|
||||
await wallet?.info.updateOtherData(
|
||||
newEntries: {
|
||||
WalletInfoKeys.enableLelantusScanning:
|
||||
!_enableLelantusScanning,
|
||||
},
|
||||
isar: ref.read(mainDBProvider).isar,
|
||||
);
|
||||
|
||||
setState(() {
|
||||
_enableLelantusScanning = !_enableLelantusScanning;
|
||||
_isUpdatingLelantusScanning = false; // Free mutex.
|
||||
});
|
||||
},
|
||||
isOn: ref.watch(
|
||||
pWalletInfo(widget.walletId)
|
||||
.select((value) => value.otherData),
|
||||
)[WalletInfoKeys.enableLelantusScanning] as bool? ??
|
||||
false,
|
||||
onValueChanged: _switchToggled,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../../db/sqlite/firo_cache.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
import '../../../../utilities/text_styles.dart';
|
||||
import '../../../../widgets/background.dart';
|
||||
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../../widgets/detail_item.dart';
|
||||
|
||||
class SparkInfoView extends ConsumerWidget {
|
||||
const SparkInfoView({
|
||||
super.key,
|
||||
});
|
||||
|
||||
static const String routeName = "/sparkInfo";
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"Spark Info",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
FutureBuilder(
|
||||
future: FiroCacheCoordinator.getSparkCacheSize(),
|
||||
builder: (_, snapshot) {
|
||||
String detail = "Loading...";
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
detail = snapshot.data ?? detail;
|
||||
}
|
||||
|
||||
return DetailItem(
|
||||
title: "Spark electrumx cache size",
|
||||
detail: detail,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import '../../../pinpad_views/lock_screen_view.dart';
|
|||
import 'delete_wallet_warning_view.dart';
|
||||
import 'lelantus_settings_view.dart';
|
||||
import 'rename_wallet_view.dart';
|
||||
import 'spark_info.dart';
|
||||
|
||||
class WalletSettingsWalletSettingsView extends ConsumerWidget {
|
||||
const WalletSettingsWalletSettingsView({
|
||||
|
@ -216,6 +217,39 @@ class WalletSettingsWalletSettingsView extends ConsumerWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: RawMaterialButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
SparkInfoView.routeName,
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 20,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"Spark info",
|
||||
style: STextStyles.titleBold12(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -16,13 +16,17 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import '../../../models/isar/models/isar_models.dart';
|
||||
import '../../../pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart';
|
||||
import '../../../pages/token_view/my_tokens_view.dart';
|
||||
import '../../../pages/wallet_view/sub_widgets/transactions_list.dart';
|
||||
import '../../../pages/wallet_view/transaction_views/all_transactions_view.dart';
|
||||
import '../../../pages/wallet_view/transaction_views/tx_v2/all_transactions_v2_view.dart';
|
||||
import '../../../pages/wallet_view/transaction_views/tx_v2/transaction_v2_list.dart';
|
||||
import '../../../providers/db/main_db_provider.dart';
|
||||
import '../../../providers/global/active_wallet_provider.dart';
|
||||
import '../../../providers/global/auto_swb_service_provider.dart';
|
||||
import '../../../providers/providers.dart';
|
||||
|
@ -35,14 +39,17 @@ import '../../../utilities/assets.dart';
|
|||
import '../../../utilities/enums/backup_frequency_type.dart';
|
||||
import '../../../utilities/enums/sync_type_enum.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/wallet_tools.dart';
|
||||
import '../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../../wallets/wallet/impl/banano_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/firo_wallet.dart';
|
||||
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../widgets/custom_buttons/blue_text_button.dart';
|
||||
import '../../../widgets/desktop/desktop_app_bar.dart';
|
||||
import '../../../widgets/desktop/desktop_scaffold.dart';
|
||||
import '../../../widgets/hover_text_field.dart';
|
||||
import '../../../widgets/rounded_white_container.dart';
|
||||
import '../../coin_control/desktop_coin_control_use_dialog.dart';
|
||||
import 'sub_widgets/desktop_wallet_features.dart';
|
||||
import 'sub_widgets/desktop_wallet_summary.dart';
|
||||
import 'sub_widgets/my_wallet.dart';
|
||||
|
@ -129,7 +136,10 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
|||
widget.eventBus != null ? widget.eventBus! : GlobalEventBus.instance;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => ref.read(currentWalletIdProvider.notifier).state = wallet.walletId,
|
||||
(_) {
|
||||
ref.read(currentWalletIdProvider.notifier).state = wallet.walletId;
|
||||
ref.read(desktopUseUTXOs.notifier).state = {};
|
||||
},
|
||||
);
|
||||
|
||||
if (!wallet.shouldAutoSync) {
|
||||
|
@ -207,17 +217,71 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
|||
),
|
||||
if (kDebugMode) const Spacer(),
|
||||
if (kDebugMode)
|
||||
Row(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
"Debug Height:",
|
||||
Row(
|
||||
children: [
|
||||
const Text(
|
||||
"dbgHeight: ",
|
||||
),
|
||||
const SizedBox(
|
||||
width: 2,
|
||||
),
|
||||
Text(
|
||||
ref
|
||||
.watch(pWalletChainHeight(widget.walletId))
|
||||
.toString(),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
width: 2,
|
||||
),
|
||||
Text(
|
||||
ref.watch(pWalletChainHeight(widget.walletId)).toString(),
|
||||
Row(
|
||||
children: [
|
||||
const Text(
|
||||
"dbgTxCount: ",
|
||||
),
|
||||
const SizedBox(
|
||||
width: 2,
|
||||
),
|
||||
Text(
|
||||
wallet.isarTransactionVersion == 2
|
||||
? ref
|
||||
.watch(mainDBProvider)
|
||||
.isar
|
||||
.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(widget.walletId)
|
||||
.countSync()
|
||||
.toString()
|
||||
: ref
|
||||
.watch(mainDBProvider)
|
||||
.isar
|
||||
.transactions
|
||||
.where()
|
||||
.walletIdEqualTo(widget.walletId)
|
||||
.countSync()
|
||||
.toString(),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (wallet.isarTransactionVersion == 2 &&
|
||||
wallet is FiroWallet)
|
||||
Row(
|
||||
children: [
|
||||
const Text(
|
||||
"dbgBal: ",
|
||||
),
|
||||
const SizedBox(
|
||||
width: 2,
|
||||
),
|
||||
Text(
|
||||
WalletDevTools.checkFiroTransactionTally(
|
||||
widget.walletId,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
|
|
|
@ -17,16 +17,13 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
import '../../../../models/isar/models/contact_entry.dart';
|
||||
import '../../../../models/paynym/paynym_account_lite.dart';
|
||||
import '../../../../models/send_view_auto_fill_data.dart';
|
||||
import '../../../../pages/send_view/confirm_transaction_view.dart';
|
||||
import '../../../../pages/send_view/sub_widgets/building_transaction_dialog.dart';
|
||||
import '../../../../pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart';
|
||||
import '../../../coin_control/desktop_coin_control_use_dialog.dart';
|
||||
import '../../../desktop_home_view.dart';
|
||||
import 'address_book_address_chooser/address_book_address_chooser.dart';
|
||||
import 'desktop_fee_dropdown.dart';
|
||||
import '../../../../providers/providers.dart';
|
||||
import '../../../../providers/ui/fee_rate_type_state_provider.dart';
|
||||
import '../../../../providers/ui/preview_tx_button_state_provider.dart';
|
||||
|
@ -70,6 +67,10 @@ import '../../../../widgets/icon_widgets/x_icon.dart';
|
|||
import '../../../../widgets/rounded_container.dart';
|
||||
import '../../../../widgets/stack_text_field.dart';
|
||||
import '../../../../widgets/textfield_icon_button.dart';
|
||||
import '../../../coin_control/desktop_coin_control_use_dialog.dart';
|
||||
import '../../../desktop_home_view.dart';
|
||||
import 'address_book_address_chooser/address_book_address_chooser.dart';
|
||||
import 'desktop_fee_dropdown.dart';
|
||||
|
||||
class DesktopSend extends ConsumerStatefulWidget {
|
||||
const DesktopSend({
|
||||
|
@ -947,7 +948,10 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
(value) => value.enableCoinControl,
|
||||
),
|
||||
) &&
|
||||
ref.watch(pWallets).getWallet(walletId) is CoinControlInterface;
|
||||
ref.watch(pWallets).getWallet(walletId) is CoinControlInterface &&
|
||||
(coin is Firo
|
||||
? ref.watch(publicPrivateBalanceStateProvider) == FiroType.public
|
||||
: true);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -1042,6 +1046,9 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
],
|
||||
onChanged: (value) {
|
||||
if (value is FiroType) {
|
||||
if (value != FiroType.public) {
|
||||
ref.read(desktopUseUTXOs.state).state = {};
|
||||
}
|
||||
setState(() {
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
value;
|
||||
|
|
|
@ -23,6 +23,7 @@ import '../../themes/stack_colors.dart';
|
|||
import '../../utilities/assets.dart';
|
||||
import '../../utilities/constants.dart';
|
||||
import '../../utilities/flutter_secure_storage_interface.dart';
|
||||
import '../../utilities/show_loading.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../widgets/desktop/desktop_app_bar.dart';
|
||||
|
@ -68,12 +69,7 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
|
|||
|
||||
bool _nextLock = false;
|
||||
|
||||
void onNextPressed() async {
|
||||
if (_nextLock) {
|
||||
return;
|
||||
}
|
||||
_nextLock = true;
|
||||
|
||||
Future<void> _onNextPressed() async {
|
||||
final String passphrase = passwordController.text;
|
||||
final String repeatPassphrase = passwordRepeatController.text;
|
||||
|
||||
|
@ -85,7 +81,6 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
|
|||
context: context,
|
||||
),
|
||||
);
|
||||
_nextLock = false;
|
||||
return;
|
||||
}
|
||||
if (passphrase != repeatPassphrase) {
|
||||
|
@ -96,19 +91,31 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
|
|||
context: context,
|
||||
),
|
||||
);
|
||||
_nextLock = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (await ref.read(storageCryptoHandlerProvider).hasPassword()) {
|
||||
throw Exception(
|
||||
"Tried creating a new password and attempted to overwrite an existing entry!",
|
||||
);
|
||||
whileFuture() async {
|
||||
if (await ref.read(storageCryptoHandlerProvider).hasPassword()) {
|
||||
throw Exception(
|
||||
"Tried creating a new password and attempted to overwrite an existing entry!",
|
||||
);
|
||||
}
|
||||
|
||||
await ref.read(storageCryptoHandlerProvider).initFromNew(passphrase);
|
||||
await (ref.read(secureStoreProvider).store as DesktopSecureStore)
|
||||
.init();
|
||||
}
|
||||
|
||||
await ref.read(storageCryptoHandlerProvider).initFromNew(passphrase);
|
||||
await (ref.read(secureStoreProvider).store as DesktopSecureStore).init();
|
||||
await showLoading(
|
||||
whileFuture: whileFuture(),
|
||||
context: context,
|
||||
message: "Initializing...",
|
||||
rootNavigator: true,
|
||||
onException: (e) {
|
||||
throw e;
|
||||
},
|
||||
);
|
||||
|
||||
// load default nodes now as node service requires storage handler to exist
|
||||
|
||||
|
@ -116,14 +123,15 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
|
|||
await ref.read(nodeServiceChangeNotifierProvider).updateDefaults();
|
||||
}
|
||||
} catch (e) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "Error: $e",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
_nextLock = false;
|
||||
if (mounted) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "Error: $e",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -152,7 +160,19 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
|
|||
),
|
||||
);
|
||||
}
|
||||
_nextLock = false;
|
||||
}
|
||||
|
||||
void _onNextPressedWrapper() async {
|
||||
if (_nextLock) {
|
||||
return;
|
||||
}
|
||||
_nextLock = true;
|
||||
|
||||
try {
|
||||
await _onNextPressed();
|
||||
} finally {
|
||||
_nextLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -464,7 +484,7 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
|
|||
: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryDisabledButtonStyle(context),
|
||||
onPressed: nextEnabled ? onNextPressed : null,
|
||||
onPressed: nextEnabled ? _onNextPressedWrapper : null,
|
||||
child: Text(
|
||||
"Next",
|
||||
style: nextEnabled
|
||||
|
|
|
@ -21,6 +21,7 @@ import '../../../providers/desktop/storage_crypto_handler_provider.dart';
|
|||
import '../../../themes/stack_colors.dart';
|
||||
import '../../../utilities/assets.dart';
|
||||
import '../../../utilities/constants.dart';
|
||||
import '../../../utilities/show_loading.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../widgets/progress_bar.dart';
|
||||
|
@ -62,7 +63,8 @@ class _SecuritySettings extends ConsumerState<SecuritySettings> {
|
|||
String passwordFeedback =
|
||||
"Add another word or two. Uncommon words are better. Use a few words, avoid common phrases. No need for symbols, digits, or uppercase letters.";
|
||||
|
||||
Future<bool> attemptChangePW() async {
|
||||
bool _changePWLock = false;
|
||||
Future<(bool, FlushBarType, String)> _attemptChangePW() async {
|
||||
final String pw = passwordCurrentController.text;
|
||||
final String pwNew = passwordController.text;
|
||||
final String pwNewRepeat = passwordRepeatController.text;
|
||||
|
@ -74,14 +76,7 @@ class _SecuritySettings extends ConsumerState<SecuritySettings> {
|
|||
if (pwNew != pwNewRepeat) {
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "New passphrase does not match!",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
return false;
|
||||
return (false, FlushBarType.warning, "New passphrase does not match!");
|
||||
} else {
|
||||
final success =
|
||||
await ref.read(storageCryptoHandlerProvider).changePassphrase(
|
||||
|
@ -92,38 +87,21 @@ class _SecuritySettings extends ConsumerState<SecuritySettings> {
|
|||
if (success) {
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.success,
|
||||
message: "Passphrase successfully changed",
|
||||
context: context,
|
||||
),
|
||||
return (
|
||||
true,
|
||||
FlushBarType.success,
|
||||
"Passphrase successfully changed"
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "Passphrase change failed",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
return false;
|
||||
return (false, FlushBarType.warning, "Passphrase change failed");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "Current passphrase is not valid!",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
return false;
|
||||
return (false, FlushBarType.warning, "Current passphrase is not valid!");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -522,12 +500,37 @@ class _SecuritySettings extends ConsumerState<SecuritySettings> {
|
|||
enabled: shouldEnableSave,
|
||||
label: "Save changes",
|
||||
onPressed: () async {
|
||||
final didChangePW =
|
||||
await attemptChangePW();
|
||||
if (didChangePW) {
|
||||
setState(() {
|
||||
changePassword = false;
|
||||
});
|
||||
if (_changePWLock) {
|
||||
return;
|
||||
}
|
||||
_changePWLock = true;
|
||||
|
||||
try {
|
||||
final (didChangePW, type, message) =
|
||||
(await showLoading(
|
||||
whileFuture: _attemptChangePW(),
|
||||
context: context,
|
||||
message: "Updating...",
|
||||
rootNavigator: true,
|
||||
))!;
|
||||
|
||||
if (mounted) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: type,
|
||||
message: message,
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (didChangePW == true) {
|
||||
setState(() {
|
||||
changePassword = false;
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
_changePWLock = false;
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
|
@ -131,6 +131,7 @@ import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_setting
|
|||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart';
|
||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/lelantus_settings_view.dart';
|
||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart';
|
||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/spark_info.dart';
|
||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart';
|
||||
import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart';
|
||||
import 'pages/special/firo_rescan_recovery_error_dialog.dart';
|
||||
|
@ -1966,6 +1967,15 @@ class RouteGenerator {
|
|||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case SparkInfoView.routeName:
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => const SparkInfoView(),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
|
||||
// == Desktop specific routes ============================================
|
||||
case CreatePasswordView.routeName:
|
||||
if (args is bool) {
|
||||
|
|
|
@ -40,7 +40,7 @@ abstract class Constants {
|
|||
// Enable Logger.print statements
|
||||
static const bool disableLogger = false;
|
||||
|
||||
static const int currentDataVersion = 12;
|
||||
static const int currentDataVersion = 13;
|
||||
|
||||
static const int rescanV1 = 1;
|
||||
|
||||
|
|
|
@ -14,5 +14,6 @@ enum LogLevel {
|
|||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Fatal;
|
||||
Fatal,
|
||||
Debug;
|
||||
}
|
||||
|
|
|
@ -13,5 +13,6 @@ export 'impl/box_shadow.dart';
|
|||
export 'impl/cl_transaction.dart';
|
||||
export 'impl/contract_abi.dart';
|
||||
export 'impl/gradient.dart';
|
||||
export 'impl/list.dart';
|
||||
export 'impl/string.dart';
|
||||
export 'impl/uint8_list.dart';
|
||||
|
|
19
lib/utilities/extensions/impl/list.dart
Normal file
19
lib/utilities/extensions/impl/list.dart
Normal file
|
@ -0,0 +1,19 @@
|
|||
extension ListExt<T> on List<T> {
|
||||
List<List<T>> chunked({required int chunkSize}) {
|
||||
final remainder = length % chunkSize;
|
||||
final count = length ~/ chunkSize;
|
||||
final List<List<T>> result = [];
|
||||
|
||||
int i = 0;
|
||||
while (i < count) {
|
||||
result.add(sublist(i, i + chunkSize));
|
||||
i++;
|
||||
}
|
||||
|
||||
if (remainder > 0) {
|
||||
result.add(sublist(i, i + remainder));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import 'dart:typed_data';
|
|||
import 'package:dart_bs58/dart_bs58.dart';
|
||||
import 'package:dart_bs58check/dart_bs58check.dart';
|
||||
import 'package:hex/hex.dart';
|
||||
|
||||
import '../extensions.dart';
|
||||
|
||||
extension StringExtensions on String {
|
||||
|
@ -27,4 +28,14 @@ extension StringExtensions on String {
|
|||
Uint8List get toUint8ListFromBase58CheckEncoded => bs58check.decode(this);
|
||||
|
||||
BigInt get toBigIntFromHex => toUint8ListFromHex.toBigInt;
|
||||
|
||||
String get toHexFromBase64 => base64Decode(LineSplitter.split(this).join())
|
||||
.map((e) => e.toRadixString(16).padLeft(2, '0'))
|
||||
.join();
|
||||
|
||||
String get toHexReversedFromBase64 =>
|
||||
base64Decode(LineSplitter.split(this).join())
|
||||
.reversed
|
||||
.map((e) => e.toRadixString(16).padLeft(2, '0'))
|
||||
.join();
|
||||
}
|
||||
|
|
|
@ -91,6 +91,19 @@ abstract class StackFileSystem {
|
|||
}
|
||||
}
|
||||
|
||||
static Future<Directory> applicationSQLiteDirectory() async {
|
||||
final root = await applicationRootDirectory();
|
||||
if (Util.isDesktop) {
|
||||
final dir = Directory("${root.path}/sqlite");
|
||||
if (!dir.existsSync()) {
|
||||
await dir.create();
|
||||
}
|
||||
return dir;
|
||||
} else {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Directory> applicationTorDirectory() async {
|
||||
final root = await applicationRootDirectory();
|
||||
if (Util.isDesktop) {
|
||||
|
|
55
lib/utilities/wallet_tools.dart
Normal file
55
lib/utilities/wallet_tools.dart
Normal file
|
@ -0,0 +1,55 @@
|
|||
import 'package:isar/isar.dart';
|
||||
|
||||
import '../db/isar/main_db.dart';
|
||||
import '../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'amount/amount.dart';
|
||||
import 'amount/amount_formatter.dart';
|
||||
import 'amount/amount_unit.dart';
|
||||
|
||||
abstract class WalletDevTools {
|
||||
static String checkFiroTransactionTally(String walletId) {
|
||||
final amtFmt = AmountFormatter(
|
||||
unit: AmountUnit.normal,
|
||||
locale: "en_US",
|
||||
coin: Firo(CryptoCurrencyNetwork.main),
|
||||
maxDecimals: 8,
|
||||
);
|
||||
|
||||
final all = MainDB.instance.isar.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.findAllSync();
|
||||
|
||||
final totalCount = all.length;
|
||||
|
||||
BigInt runningBalance = BigInt.zero;
|
||||
for (final tx in all) {
|
||||
final ownIns = tx.inputs
|
||||
.where((e) => e.walletOwns)
|
||||
.map((e) => e.value)
|
||||
.fold(BigInt.zero, (p, e) => p + e);
|
||||
runningBalance -= ownIns;
|
||||
|
||||
final ownOuts = tx.outputs
|
||||
.where((e) => e.walletOwns)
|
||||
.map((e) => e.value)
|
||||
.fold(BigInt.zero, (p, e) => p + e);
|
||||
runningBalance += ownOuts;
|
||||
}
|
||||
|
||||
final balanceAccordingToTxHistory = Amount(
|
||||
rawValue: runningBalance,
|
||||
fractionDigits: 8,
|
||||
);
|
||||
|
||||
print("======== $walletId =============");
|
||||
print("totalTxCount: $totalCount");
|
||||
print(
|
||||
"balanceAccordingToTxns: ${amtFmt.format(balanceAccordingToTxHistory)}",
|
||||
);
|
||||
print("==================================================");
|
||||
|
||||
return amtFmt.format(balanceAccordingToTxHistory);
|
||||
}
|
||||
}
|
|
@ -282,10 +282,8 @@ class Ecash extends Bip39HDCurrency with ElectrumXCurrencyInterface {
|
|||
switch (network) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
return NodeModel(
|
||||
// host: "ecash.stackwallet.com",
|
||||
// port: 59002,
|
||||
host: "electrum.bitcoinabc.org",
|
||||
port: 50002,
|
||||
host: "ecash.stackwallet.com",
|
||||
port: 59002,
|
||||
name: DefaultNodes.defaultName,
|
||||
id: DefaultNodes.buildId(this),
|
||||
useSSL: true,
|
||||
|
|
|
@ -508,4 +508,6 @@ abstract class WalletInfoKeys {
|
|||
static const String lelantusCoinIsarRescanRequired =
|
||||
"lelantusCoinIsarRescanRequired";
|
||||
static const String enableLelantusScanning = "enableLelantusScanningKey";
|
||||
static const String firoSparkCacheSetTimestampCache =
|
||||
"firoSparkCacheSetTimestampCacheKey";
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:decimal/decimal.dart';
|
|||
import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
import '../../../db/sqlite/firo_cache.dart';
|
||||
import '../../../models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||
import '../../../models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||
import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
|
@ -20,6 +21,7 @@ import '../../isar/models/spark_coin.dart';
|
|||
import '../../isar/models/wallet_info.dart';
|
||||
import '../../models/tx_data.dart';
|
||||
import '../intermediate/bip39_hd_wallet.dart';
|
||||
import '../wallet_mixin_interfaces/coin_control_interface.dart';
|
||||
import '../wallet_mixin_interfaces/electrumx_interface.dart';
|
||||
import '../wallet_mixin_interfaces/lelantus_interface.dart';
|
||||
import '../wallet_mixin_interfaces/spark_interface.dart';
|
||||
|
@ -27,7 +29,11 @@ import '../wallet_mixin_interfaces/spark_interface.dart';
|
|||
const sparkStartBlock = 819300; // (approx 18 Jan 2024)
|
||||
|
||||
class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
||||
with ElectrumXInterface<T>, LelantusInterface<T>, SparkInterface<T> {
|
||||
with
|
||||
ElectrumXInterface<T>,
|
||||
LelantusInterface<T>,
|
||||
SparkInterface<T>,
|
||||
CoinControlInterface<T> {
|
||||
// IMPORTANT: The order of the above mixins matters.
|
||||
// SparkInterface MUST come after LelantusInterface.
|
||||
|
||||
|
@ -587,6 +593,15 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
|||
|
||||
@override
|
||||
Future<void> recover({required bool isRescan}) async {
|
||||
// reset last checked values
|
||||
await info.updateOtherData(
|
||||
newEntries: {
|
||||
WalletInfoKeys.firoSparkCacheSetTimestampCache: <String, int>{},
|
||||
},
|
||||
isar: mainDB.isar,
|
||||
);
|
||||
|
||||
final start = DateTime.now();
|
||||
final root = await getRootHDNode();
|
||||
|
||||
final List<Future<({int index, List<Address> addresses})>> receiveFutures =
|
||||
|
@ -610,37 +625,36 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
|||
await mainDB.deleteWalletBlockchainData(walletId);
|
||||
}
|
||||
|
||||
// Parse otherDataJsonString to get the enableLelantusScanning value.
|
||||
bool? enableLelantusScanning = false;
|
||||
if (info.otherDataJsonString != null) {
|
||||
final otherDataJson = json.decode(info.otherDataJsonString!);
|
||||
enableLelantusScanning =
|
||||
otherDataJson[WalletInfoKeys.enableLelantusScanning] as bool? ??
|
||||
false;
|
||||
}
|
||||
|
||||
// lelantus
|
||||
int? latestSetId;
|
||||
Future<Map<int, dynamic>>? setDataMapFuture;
|
||||
Future<List<String>>? usedSerialNumbersFuture;
|
||||
final List<Future<dynamic>> lelantusFutures = [];
|
||||
final enableLelantusScanning =
|
||||
info.otherData[WalletInfoKeys.enableLelantusScanning] as bool? ??
|
||||
false;
|
||||
if (enableLelantusScanning) {
|
||||
latestSetId = await electrumXClient.getLelantusLatestCoinId();
|
||||
setDataMapFuture = getSetDataMap(latestSetId);
|
||||
usedSerialNumbersFuture = electrumXCachedClient.getUsedCoinSerials(
|
||||
cryptoCurrency: info.coin,
|
||||
lelantusFutures.add(
|
||||
electrumXCachedClient.getUsedCoinSerials(
|
||||
cryptoCurrency: info.coin,
|
||||
),
|
||||
);
|
||||
lelantusFutures.add(getSetDataMap(latestSetId));
|
||||
}
|
||||
|
||||
// spark
|
||||
final latestSparkCoinId = await electrumXClient.getSparkLatestCoinId();
|
||||
final sparkAnonSetFuture = electrumXCachedClient.getSparkAnonymitySet(
|
||||
groupId: latestSparkCoinId.toString(),
|
||||
cryptoCurrency: info.coin,
|
||||
useOnlyCacheIfNotEmpty: false,
|
||||
);
|
||||
final List<Future<void>> sparkAnonSetFutures = [];
|
||||
for (int i = 1; i <= latestSparkCoinId; i++) {
|
||||
sparkAnonSetFutures.add(
|
||||
FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId(
|
||||
i,
|
||||
electrumXClient,
|
||||
),
|
||||
);
|
||||
}
|
||||
final sparkUsedCoinTagsFuture =
|
||||
electrumXCachedClient.getSparkUsedCoinsTags(
|
||||
cryptoCurrency: info.coin,
|
||||
FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags(
|
||||
electrumXClient,
|
||||
);
|
||||
|
||||
// receiving addresses
|
||||
|
@ -749,14 +763,13 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
|||
updateUTXOs(),
|
||||
]);
|
||||
|
||||
List<Future<dynamic>> futures = [];
|
||||
|
||||
futures.add(sparkAnonSetFuture);
|
||||
futures.add(sparkUsedCoinTagsFuture);
|
||||
final List<Future<dynamic>> futures = [];
|
||||
if (enableLelantusScanning) {
|
||||
futures.add(usedSerialNumbersFuture!);
|
||||
futures.add(setDataMapFuture!);
|
||||
futures.add(lelantusFutures[0]);
|
||||
futures.add(lelantusFutures[1]);
|
||||
}
|
||||
futures.add(sparkUsedCoinTagsFuture);
|
||||
futures.addAll(sparkAnonSetFutures);
|
||||
|
||||
final futureResults = await Future.wait(futures);
|
||||
|
||||
|
@ -764,29 +777,22 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
|||
Set<String>? usedSerialsSet;
|
||||
Map<dynamic, dynamic>? setDataMap;
|
||||
if (enableLelantusScanning) {
|
||||
usedSerialsSet = (futureResults[2] as List<String>).toSet();
|
||||
setDataMap = futureResults[3] as Map<dynamic, dynamic>;
|
||||
usedSerialsSet = (futureResults[0] as List<String>).toSet();
|
||||
setDataMap = futureResults[1] as Map<dynamic, dynamic>;
|
||||
}
|
||||
|
||||
// spark
|
||||
final sparkAnonymitySet = futureResults[0] as Map<String, dynamic>;
|
||||
final sparkSpentCoinTags = futureResults[1] as Set<String>;
|
||||
|
||||
if (Util.isDesktop) {
|
||||
List<Future<dynamic>> futures = [];
|
||||
if (enableLelantusScanning) {
|
||||
futures.add(recoverLelantusWallet(
|
||||
latestSetId: latestSetId!,
|
||||
usedSerialNumbers: usedSerialsSet!,
|
||||
setDataMap: setDataMap!,
|
||||
));
|
||||
}
|
||||
futures.add(recoverSparkWallet(
|
||||
anonymitySet: sparkAnonymitySet,
|
||||
spentCoinTags: sparkSpentCoinTags,
|
||||
));
|
||||
|
||||
await Future.wait(futures);
|
||||
await Future.wait([
|
||||
if (enableLelantusScanning)
|
||||
recoverLelantusWallet(
|
||||
latestSetId: latestSetId!,
|
||||
usedSerialNumbers: usedSerialsSet!,
|
||||
setDataMap: setDataMap!,
|
||||
),
|
||||
recoverSparkWallet(
|
||||
latestSparkCoinId: latestSparkCoinId,
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
if (enableLelantusScanning) {
|
||||
await recoverLelantusWallet(
|
||||
|
@ -796,13 +802,17 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
|
|||
);
|
||||
}
|
||||
await recoverSparkWallet(
|
||||
anonymitySet: sparkAnonymitySet,
|
||||
spentCoinTags: sparkSpentCoinTags,
|
||||
latestSparkCoinId: latestSparkCoinId,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
unawaited(refresh());
|
||||
Logging.instance.log(
|
||||
"Firo recover for "
|
||||
"${info.name}: ${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception rethrown from electrumx_mixin recover(): $e\n$s",
|
||||
|
|
|
@ -474,6 +474,7 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
if (refreshMutex.isLocked) {
|
||||
return;
|
||||
}
|
||||
final start = DateTime.now();
|
||||
|
||||
try {
|
||||
// this acquire should be almost instant due to above check.
|
||||
|
@ -619,6 +620,12 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
);
|
||||
} finally {
|
||||
refreshMutex.release();
|
||||
|
||||
Logging.instance.log(
|
||||
"Refresh for "
|
||||
"${info.name}: ${DateTime.now().difference(start)}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
import '../../../db/sqlite/firo_cache.dart';
|
||||
import '../../../models/balance.dart';
|
||||
import '../../../models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||
import '../../../models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||
|
@ -20,6 +21,7 @@ import '../../../utilities/logger.dart';
|
|||
import '../../crypto_currency/crypto_currency.dart';
|
||||
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
|
||||
import '../../isar/models/spark_coin.dart';
|
||||
import '../../isar/models/wallet_info.dart';
|
||||
import '../../models/tx_data.dart';
|
||||
import '../intermediate/bip39_hd_wallet.dart';
|
||||
import 'electrumx_interface.dart';
|
||||
|
@ -259,17 +261,39 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
|||
final List<Map<String, dynamic>> setMaps = [];
|
||||
final List<({int groupId, String blockHash})> idAndBlockHashes = [];
|
||||
for (int i = 1; i <= currentId; i++) {
|
||||
final set = await electrumXCachedClient.getSparkAnonymitySet(
|
||||
groupId: i.toString(),
|
||||
cryptoCurrency: info.coin,
|
||||
useOnlyCacheIfNotEmpty: true,
|
||||
final resultSet = await FiroCacheCoordinator.getSetCoinsForGroupId(i);
|
||||
if (resultSet.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final info = await FiroCacheCoordinator.getLatestSetInfoForGroupId(
|
||||
i,
|
||||
);
|
||||
set["coinGroupID"] = i;
|
||||
setMaps.add(set);
|
||||
if (info == null) {
|
||||
throw Exception("The `info` should never be null here");
|
||||
}
|
||||
|
||||
final Map<String, dynamic> setData = {
|
||||
"blockHash": info.blockHash,
|
||||
"setHash": info.setHash,
|
||||
"coinGroupID": i,
|
||||
"coins": resultSet
|
||||
.map(
|
||||
(row) => [
|
||||
row["serialized"] as String,
|
||||
row["txHash"] as String,
|
||||
row["context"] as String,
|
||||
],
|
||||
)
|
||||
.toList(),
|
||||
};
|
||||
|
||||
setData["coinGroupID"] = i;
|
||||
setMaps.add(setData);
|
||||
idAndBlockHashes.add(
|
||||
(
|
||||
groupId: i,
|
||||
blockHash: set["blockHash"] as String,
|
||||
blockHash: setData["blockHash"] as String,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -607,79 +631,42 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
|
||||
Future<void> refreshSparkData() async {
|
||||
final sparkAddresses = await mainDB.isar.addresses
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.typeEqualTo(AddressType.spark)
|
||||
.findAll();
|
||||
|
||||
final Set<String> paths =
|
||||
sparkAddresses.map((e) => e.derivationPath!.value).toSet();
|
||||
|
||||
try {
|
||||
final latestSparkCoinId = await electrumXClient.getSparkLatestCoinId();
|
||||
|
||||
final anonymitySetFuture = electrumXCachedClient.getSparkAnonymitySet(
|
||||
groupId: latestSparkCoinId.toString(),
|
||||
cryptoCurrency: info.coin,
|
||||
useOnlyCacheIfNotEmpty: false,
|
||||
);
|
||||
|
||||
final spentCoinTagsFuture = electrumXCachedClient.getSparkUsedCoinsTags(
|
||||
cryptoCurrency: info.coin,
|
||||
);
|
||||
|
||||
final futureResults = await Future.wait([
|
||||
anonymitySetFuture,
|
||||
spentCoinTagsFuture,
|
||||
]);
|
||||
|
||||
final anonymitySet = futureResults[0] as Map<String, dynamic>;
|
||||
final spentCoinTags = futureResults[1] as Set<String>;
|
||||
|
||||
final List<SparkCoin> myCoins = [];
|
||||
|
||||
if (anonymitySet["coins"] is List &&
|
||||
(anonymitySet["coins"] as List).isNotEmpty) {
|
||||
final root = await getRootHDNode();
|
||||
final privateKeyHexSet = paths
|
||||
.map(
|
||||
(e) => root.derivePath(e).privateKey.data.toHex,
|
||||
)
|
||||
.toSet();
|
||||
|
||||
final identifiedCoins = await compute(
|
||||
_identifyCoins,
|
||||
(
|
||||
anonymitySetCoins: anonymitySet["coins"] as List,
|
||||
groupId: latestSparkCoinId,
|
||||
spentCoinTags: spentCoinTags,
|
||||
privateKeyHexSet: privateKeyHexSet,
|
||||
walletId: walletId,
|
||||
isTestNet: cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
||||
),
|
||||
);
|
||||
|
||||
myCoins.addAll(identifiedCoins);
|
||||
}
|
||||
|
||||
// check current coins
|
||||
final currentCoins = await mainDB.isar.sparkCoins
|
||||
.where()
|
||||
.walletIdEqualToAnyLTagHash(walletId)
|
||||
.filter()
|
||||
.isUsedEqualTo(false)
|
||||
.findAll();
|
||||
for (final coin in currentCoins) {
|
||||
if (spentCoinTags.contains(coin.lTagHash)) {
|
||||
myCoins.add(coin.copyWith(isUsed: true));
|
||||
// start by checking if any previous sets are missing from db and add the
|
||||
// missing groupIds to the list if sets to check and update
|
||||
final latestGroupId = await electrumXClient.getSparkLatestCoinId();
|
||||
final List<int> groupIds = [];
|
||||
if (latestGroupId > 1) {
|
||||
for (int id = 1; id < latestGroupId; id++) {
|
||||
final setExists =
|
||||
await FiroCacheCoordinator.checkSetInfoForGroupIdExists(
|
||||
id,
|
||||
);
|
||||
if (!setExists) {
|
||||
groupIds.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
groupIds.add(latestGroupId);
|
||||
|
||||
// update wallet spark coins in isar
|
||||
await _addOrUpdateSparkCoins(myCoins);
|
||||
// start fetch and update process for each set groupId as required
|
||||
final possibleFutures = groupIds.map(
|
||||
(e) =>
|
||||
FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId(
|
||||
e,
|
||||
electrumXClient,
|
||||
),
|
||||
);
|
||||
|
||||
// wait for each fetch and update to complete
|
||||
await Future.wait([
|
||||
...possibleFutures,
|
||||
FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags(
|
||||
electrumXClient,
|
||||
),
|
||||
]);
|
||||
|
||||
await _checkAndUpdateCoins();
|
||||
// refresh spark balance
|
||||
await refreshSparkBalance();
|
||||
} catch (e, s) {
|
||||
|
@ -737,8 +724,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
|||
/// Should only be called within the standard wallet [recover] function due to
|
||||
/// mutex locking. Otherwise behaviour MAY be undefined.
|
||||
Future<void> recoverSparkWallet({
|
||||
required Map<dynamic, dynamic> anonymitySet,
|
||||
required Set<String> spentCoinTags,
|
||||
required int latestSparkCoinId,
|
||||
}) async {
|
||||
// generate spark addresses if non existing
|
||||
if (await getCurrentReceivingSparkAddress() == null) {
|
||||
|
@ -746,35 +732,8 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
|||
await mainDB.putAddress(address);
|
||||
}
|
||||
|
||||
final sparkAddresses = await mainDB.isar.addresses
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.typeEqualTo(AddressType.spark)
|
||||
.findAll();
|
||||
|
||||
final Set<String> paths =
|
||||
sparkAddresses.map((e) => e.derivationPath!.value).toSet();
|
||||
|
||||
try {
|
||||
final root = await getRootHDNode();
|
||||
final privateKeyHexSet =
|
||||
paths.map((e) => root.derivePath(e).privateKey.data.toHex).toSet();
|
||||
|
||||
final myCoins = await compute(
|
||||
_identifyCoins,
|
||||
(
|
||||
anonymitySetCoins: anonymitySet["coins"] as List,
|
||||
groupId: anonymitySet["coinGroupID"] as int,
|
||||
spentCoinTags: spentCoinTags,
|
||||
privateKeyHexSet: privateKeyHexSet,
|
||||
walletId: walletId,
|
||||
isTestNet: cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
||||
),
|
||||
);
|
||||
|
||||
// update wallet spark coins in isar
|
||||
await _addOrUpdateSparkCoins(myCoins);
|
||||
await _checkAndUpdateCoins();
|
||||
|
||||
// refresh spark balance
|
||||
await refreshSparkBalance();
|
||||
|
@ -787,6 +746,115 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _checkAndUpdateCoins() async {
|
||||
final sparkAddresses = await mainDB.isar.addresses
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.typeEqualTo(AddressType.spark)
|
||||
.findAll();
|
||||
final root = await getRootHDNode();
|
||||
final Set<String> privateKeyHexSet = sparkAddresses
|
||||
.map(
|
||||
(e) => root.derivePath(e.derivationPath!.value).privateKey.data.toHex,
|
||||
)
|
||||
.toSet();
|
||||
|
||||
final Map<int, List<List<String>>> rawCoinsBySetId = {};
|
||||
|
||||
final groupIdTimestampUTCMap =
|
||||
info.otherData[WalletInfoKeys.firoSparkCacheSetTimestampCache]
|
||||
as Map? ??
|
||||
{};
|
||||
|
||||
final latestSparkCoinId = await electrumXClient.getSparkLatestCoinId();
|
||||
for (int i = 1; i <= latestSparkCoinId; i++) {
|
||||
final lastCheckedTimeStampUTC =
|
||||
groupIdTimestampUTCMap[i.toString()] as int? ?? 0;
|
||||
final info = await FiroCacheCoordinator.getLatestSetInfoForGroupId(
|
||||
i,
|
||||
);
|
||||
final anonymitySetResult =
|
||||
await FiroCacheCoordinator.getSetCoinsForGroupId(
|
||||
i,
|
||||
newerThanTimeStamp: lastCheckedTimeStampUTC,
|
||||
);
|
||||
final coinsRaw = anonymitySetResult
|
||||
.map(
|
||||
(row) => [
|
||||
row["serialized"] as String,
|
||||
row["txHash"] as String,
|
||||
row["context"] as String,
|
||||
],
|
||||
)
|
||||
.toList();
|
||||
|
||||
if (coinsRaw.isNotEmpty) {
|
||||
rawCoinsBySetId[i] = coinsRaw;
|
||||
}
|
||||
|
||||
groupIdTimestampUTCMap[i.toString()] = max(
|
||||
lastCheckedTimeStampUTC,
|
||||
info?.timestampUTC ?? lastCheckedTimeStampUTC,
|
||||
);
|
||||
}
|
||||
|
||||
await info.updateOtherData(
|
||||
newEntries: {
|
||||
WalletInfoKeys.firoSparkCacheSetTimestampCache: groupIdTimestampUTCMap,
|
||||
},
|
||||
isar: mainDB.isar,
|
||||
);
|
||||
|
||||
final List<SparkCoin> newlyIdCoins = [];
|
||||
for (final groupId in rawCoinsBySetId.keys) {
|
||||
final myCoins = await compute(
|
||||
_identifyCoins,
|
||||
(
|
||||
anonymitySetCoins: rawCoinsBySetId[groupId]!,
|
||||
groupId: groupId,
|
||||
privateKeyHexSet: privateKeyHexSet,
|
||||
walletId: walletId,
|
||||
isTestNet: cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
||||
),
|
||||
);
|
||||
newlyIdCoins.addAll(myCoins);
|
||||
}
|
||||
|
||||
await _checkAndMarkCoinsUsedInDB(coinsNotInDbYet: newlyIdCoins);
|
||||
}
|
||||
|
||||
Future<void> _checkAndMarkCoinsUsedInDB({
|
||||
List<SparkCoin> coinsNotInDbYet = const [],
|
||||
}) async {
|
||||
final List<SparkCoin> coins = await mainDB.isar.sparkCoins
|
||||
.where()
|
||||
.walletIdEqualToAnyLTagHash(walletId)
|
||||
.filter()
|
||||
.isUsedEqualTo(false)
|
||||
.findAll();
|
||||
|
||||
final List<SparkCoin> coinsToWrite = [];
|
||||
|
||||
final spentCoinTags = await FiroCacheCoordinator.getUsedCoinTags(0);
|
||||
|
||||
for (final coin in coins) {
|
||||
if (spentCoinTags.contains(coin.lTagHash)) {
|
||||
coinsToWrite.add(coin.copyWith(isUsed: true));
|
||||
}
|
||||
}
|
||||
for (final coin in coinsNotInDbYet) {
|
||||
if (spentCoinTags.contains(coin.lTagHash)) {
|
||||
coinsToWrite.add(coin.copyWith(isUsed: true));
|
||||
} else {
|
||||
coinsToWrite.add(coin);
|
||||
}
|
||||
}
|
||||
|
||||
// update wallet spark coins in isar
|
||||
await _addOrUpdateSparkCoins(coinsToWrite);
|
||||
}
|
||||
|
||||
// modelled on CSparkWallet::CreateSparkMintTransactions https://github.com/firoorg/firo/blob/39c41e5e7ec634ced3700fe3f4f5509dc2e480d0/src/spark/sparkwallet.cpp#L752
|
||||
Future<List<TxData>> _createSparkMintTransactions({
|
||||
required List<UTXO> availableUtxos,
|
||||
|
@ -1630,12 +1698,6 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
|
|||
);
|
||||
}
|
||||
|
||||
String base64ToReverseHex(String source) =>
|
||||
base64Decode(LineSplitter.split(source).join())
|
||||
.reversed
|
||||
.map((e) => e.toRadixString(16).padLeft(2, '0'))
|
||||
.join();
|
||||
|
||||
/// Top level function which should be called wrapped in [compute]
|
||||
Future<
|
||||
({
|
||||
|
@ -1701,7 +1763,6 @@ Future<List<SparkCoin>> _identifyCoins(
|
|||
({
|
||||
List<dynamic> anonymitySetCoins,
|
||||
int groupId,
|
||||
Set<String> spentCoinTags,
|
||||
Set<String> privateKeyHexSet,
|
||||
String walletId,
|
||||
bool isTestNet,
|
||||
|
@ -1718,7 +1779,7 @@ Future<List<SparkCoin>> _identifyCoins(
|
|||
}
|
||||
|
||||
final serializedCoinB64 = data[0];
|
||||
final txHash = base64ToReverseHex(data[1]);
|
||||
final txHash = data[1].toHexReversedFromBase64;
|
||||
final contextB64 = data[2];
|
||||
|
||||
final coin = LibSpark.identifyAndRecoverCoin(
|
||||
|
@ -1744,7 +1805,7 @@ Future<List<SparkCoin>> _identifyCoins(
|
|||
SparkCoin(
|
||||
walletId: args.walletId,
|
||||
type: coinType,
|
||||
isUsed: args.spentCoinTags.contains(coin.lTagHash!),
|
||||
isUsed: false,
|
||||
groupId: args.groupId,
|
||||
nonce: coin.nonceHex?.toUint8ListFromHex,
|
||||
address: coin.address!,
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <flutter_libmonero/flutter_libmonero_plugin.h>
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
|
||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||
#include <stack_wallet_backup/stack_wallet_backup_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
#include <window_size/window_size_plugin.h>
|
||||
|
@ -35,6 +36,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||
g_autoptr(FlPluginRegistrar) isar_flutter_libs_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin");
|
||||
isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar);
|
||||
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
|
||||
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
|
||||
g_autoptr(FlPluginRegistrar) stack_wallet_backup_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "StackWalletBackupPlugin");
|
||||
stack_wallet_backup_plugin_register_with_registrar(stack_wallet_backup_registrar);
|
||||
|
|
|
@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
flutter_libmonero
|
||||
flutter_secure_storage_linux
|
||||
isar_flutter_libs
|
||||
sqlite3_flutter_libs
|
||||
stack_wallet_backup
|
||||
url_launcher_linux
|
||||
window_size
|
||||
|
|
|
@ -17,6 +17,7 @@ import lelantus
|
|||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
import share_plus
|
||||
import sqlite3_flutter_libs
|
||||
import stack_wallet_backup
|
||||
import url_launcher_macos
|
||||
import wakelock_macos
|
||||
|
@ -35,6 +36,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
|
||||
StackWalletBackupPlugin.register(with: registry.registrar(forPlugin: "StackWalletBackupPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin"))
|
||||
|
|
|
@ -34,6 +34,21 @@ PODS:
|
|||
- ReachabilitySwift (5.0.0)
|
||||
- share_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- sqlite3 (3.46.0):
|
||||
- sqlite3/common (= 3.46.0)
|
||||
- sqlite3/common (3.46.0)
|
||||
- sqlite3/fts5 (3.46.0):
|
||||
- sqlite3/common
|
||||
- sqlite3/perf-threadsafe (3.46.0):
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.46.0):
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- FlutterMacOS
|
||||
- sqlite3 (~> 3.46.0)
|
||||
- sqlite3/fts5
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
- stack_wallet_backup (0.0.1):
|
||||
- FlutterMacOS
|
||||
- tor_ffi_plugin (0.0.1)
|
||||
|
@ -61,6 +76,7 @@ DEPENDENCIES:
|
|||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
||||
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos`)
|
||||
- stack_wallet_backup (from `Flutter/ephemeral/.symlinks/plugins/stack_wallet_backup/macos`)
|
||||
- tor_ffi_plugin (from `Flutter/ephemeral/.symlinks/plugins/tor_ffi_plugin/macos`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
|
@ -70,6 +86,7 @@ DEPENDENCIES:
|
|||
SPEC REPOS:
|
||||
trunk:
|
||||
- ReachabilitySwift
|
||||
- sqlite3
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
coinlib_flutter:
|
||||
|
@ -104,6 +121,8 @@ EXTERNAL SOURCES:
|
|||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
share_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
||||
sqlite3_flutter_libs:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos
|
||||
stack_wallet_backup:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/stack_wallet_backup/macos
|
||||
tor_ffi_plugin:
|
||||
|
@ -133,6 +152,8 @@ SPEC CHECKSUMS:
|
|||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7
|
||||
sqlite3: 154b084339ede06960a5b3c8160066adc9176b7d
|
||||
sqlite3_flutter_libs: 1be4459672f8168ded2d8667599b8e3ca5e72b83
|
||||
stack_wallet_backup: 6ebc60b1bdcf11cf1f1cbad9aa78332e1e15778c
|
||||
tor_ffi_plugin: 2566c1ed174688cca560fa0c64b7a799c66f07cb
|
||||
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
||||
|
|
24
pubspec.lock
24
pubspec.lock
|
@ -689,8 +689,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "439727b278250c61a291f5335c298c0f2d952517"
|
||||
resolved-ref: "439727b278250c61a291f5335c298c0f2d952517"
|
||||
ref: "7a11d0cadf8c7a6a5d5144dab18cef9536aa5943"
|
||||
resolved-ref: "7a11d0cadf8c7a6a5d5144dab18cef9536aa5943"
|
||||
url: "https://github.com/cypherstack/flutter_libsparkmobile.git"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
|
@ -1194,12 +1194,12 @@ packages:
|
|||
source: hosted
|
||||
version: "0.2.0"
|
||||
monero:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "6a17a405a1a260fa228b2f4fc94044088a4335ac"
|
||||
resolved-ref: "6a17a405a1a260fa228b2f4fc94044088a4335ac"
|
||||
url: "https://git.mrcyjanek.net/mrcyjanek/monero.dart"
|
||||
url: "https://www.github.com/mrcyjanek/monero.dart"
|
||||
source: git
|
||||
version: "0.0.0"
|
||||
mutex:
|
||||
|
@ -1633,6 +1633,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
sqlite3:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqlite3
|
||||
sha256: b384f598b813b347c5a7e5ffad82cbaff1bec3d1561af267041e66f6f0899295
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
sqlite3_flutter_libs:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqlite3_flutter_libs
|
||||
sha256: "1e62698dc1ab396152ccaf3b3990d826244e9f3c8c39b51805f209adcd6dbea3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.22"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -14,7 +14,7 @@ PLUGINS_DIR=../../crypto_plugins
|
|||
|
||||
(cd "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android && ./build_all.sh )
|
||||
(cd "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android && ./build_all.sh )
|
||||
(cd "${PLUGINS_DIR}"/flutter_libmonero/scripts/android/ && ./build_all.sh )
|
||||
(cd "${PLUGINS_DIR}"/flutter_libmonero/scripts/android/ && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd "${PLUGINS_DIR}"/frostdart/scripts/android && ./build_all.sh )
|
||||
|
||||
|
|
|
@ -14,11 +14,11 @@ mkdir -p build
|
|||
|
||||
PLUGINS_DIR=../../crypto_plugins
|
||||
|
||||
(cd "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android && ./build_all.sh ) &
|
||||
(cd "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android && ./build_all.sh ) &
|
||||
(cd "${PLUGINS_DIR}"/flutter_libmonero/scripts/android/ && ./build_all.sh ) &&
|
||||
set_rust_to_1720 &&
|
||||
(cd "${PLUGINS_DIR}"/frostdart/scripts/android && ./build_all.sh ) &
|
||||
(cd "${PLUGINS_DIR}"/flutter_liblelantus/scripts/android && ./build_all.sh )
|
||||
(cd "${PLUGINS_DIR}"/flutter_libepiccash/scripts/android && ./build_all.sh )
|
||||
(cd "${PLUGINS_DIR}"/flutter_libmonero/scripts/android/ && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd "${PLUGINS_DIR}"/frostdart/scripts/android && ./build_all.sh )
|
||||
|
||||
wait
|
||||
echo "Done building"
|
||||
|
|
|
@ -44,24 +44,24 @@ const ({String light, String dark})? _appIconAsset = null;
|
|||
|
||||
final List<CryptoCurrency> _supportedCoins = List.unmodifiable([
|
||||
Bitcoin(CryptoCurrencyNetwork.main),
|
||||
BitcoinFrost(CryptoCurrencyNetwork.main),
|
||||
Litecoin(CryptoCurrencyNetwork.main),
|
||||
Monero(CryptoCurrencyNetwork.main),
|
||||
Banano(CryptoCurrencyNetwork.main),
|
||||
Bitcoincash(CryptoCurrencyNetwork.main),
|
||||
BitcoinFrost(CryptoCurrencyNetwork.main),
|
||||
Dogecoin(CryptoCurrencyNetwork.main),
|
||||
Epiccash(CryptoCurrencyNetwork.main),
|
||||
Ecash(CryptoCurrencyNetwork.main),
|
||||
Epiccash(CryptoCurrencyNetwork.main),
|
||||
Ethereum(CryptoCurrencyNetwork.main),
|
||||
Firo(CryptoCurrencyNetwork.main),
|
||||
Monero(CryptoCurrencyNetwork.main),
|
||||
Litecoin(CryptoCurrencyNetwork.main),
|
||||
Nano(CryptoCurrencyNetwork.main),
|
||||
Namecoin(CryptoCurrencyNetwork.main),
|
||||
Particl(CryptoCurrencyNetwork.main),
|
||||
Peercoin(CryptoCurrencyNetwork.main),
|
||||
Solana(CryptoCurrencyNetwork.main),
|
||||
Stellar(CryptoCurrencyNetwork.main),
|
||||
Tezos(CryptoCurrencyNetwork.main),
|
||||
Wownero(CryptoCurrencyNetwork.main),
|
||||
Namecoin(CryptoCurrencyNetwork.main),
|
||||
Nano(CryptoCurrencyNetwork.main),
|
||||
Banano(CryptoCurrencyNetwork.main),
|
||||
Bitcoin(CryptoCurrencyNetwork.test),
|
||||
BitcoinFrost(CryptoCurrencyNetwork.test),
|
||||
Litecoin(CryptoCurrencyNetwork.test),
|
||||
|
|
|
@ -33,7 +33,7 @@ dependencies:
|
|||
flutter_libsparkmobile:
|
||||
git:
|
||||
url: https://github.com/cypherstack/flutter_libsparkmobile.git
|
||||
ref: 439727b278250c61a291f5335c298c0f2d952517
|
||||
ref: 7a11d0cadf8c7a6a5d5144dab18cef9536aa5943
|
||||
|
||||
flutter_libmonero:
|
||||
path: ./crypto_plugins/flutter_libmonero
|
||||
|
@ -49,7 +49,7 @@ dependencies:
|
|||
|
||||
monero:
|
||||
git:
|
||||
url: https://git.mrcyjanek.net/mrcyjanek/monero.dart
|
||||
url: https://www.github.com/mrcyjanek/monero.dart
|
||||
ref: 6a17a405a1a260fa228b2f4fc94044088a4335ac
|
||||
|
||||
flutter_libepiccash:
|
||||
|
@ -183,6 +183,8 @@ dependencies:
|
|||
ref: a83e375678eb22fe544dc125d29bbec0fb833882
|
||||
path: packages/solana
|
||||
calendar_date_picker2: ^1.0.2
|
||||
sqlite3: ^2.4.3
|
||||
sqlite3_flutter_libs: ^0.5.22
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -209,6 +211,11 @@ flutter_native_splash:
|
|||
|
||||
dependency_overrides:
|
||||
|
||||
monero:
|
||||
git:
|
||||
url: https://www.github.com/mrcyjanek/monero.dart
|
||||
ref: 6a17a405a1a260fa228b2f4fc94044088a4335ac
|
||||
|
||||
bip47:
|
||||
git:
|
||||
url: https://github.com/cypherstack/bip47.git
|
||||
|
|
|
@ -14,11 +14,11 @@ rustup target add x86_64-apple-ios
|
|||
rustup target add aarch64-apple-ios
|
||||
rustup target add x86_64-apple-ios
|
||||
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_libmonero/scripts/ios/ && ./build_all.sh ) &&
|
||||
set_rust_to_1720 &&
|
||||
(cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libmonero/scripts/ios/ && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh )
|
||||
|
||||
wait
|
||||
echo "Done building"
|
||||
|
|
|
@ -16,11 +16,11 @@ rustup target add x86_64-apple-ios
|
|||
rustup target add aarch64-apple-ios
|
||||
rustup target add x86_64-apple-ios
|
||||
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_libmonero/scripts/ios/ && ./build_all.sh ) &&
|
||||
set_rust_to_1720 &&
|
||||
(cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/ios && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/ios && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libmonero/scripts/ios/ && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/ios && ./build_all.sh )
|
||||
|
||||
wait
|
||||
echo "Done building"
|
||||
|
|
|
@ -14,11 +14,11 @@ set_rust_to_1671
|
|||
# flutter-elinux build linux --dart-define="IS_ARM=true"
|
||||
mkdir -p build
|
||||
./build_secure_storage_deps.sh &
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_libmonero/scripts/linux && ./build_monero_all.sh && ./build_sharedfile.sh )
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/linux && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/linux && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libmonero/scripts/linux && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/frostdart/scripts/linux && ./build_all.sh )
|
||||
|
||||
./build_secp256k1.sh
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ set_rust_to_1671
|
|||
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libmonero/scripts/macos/ && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libmonero/scripts/macos/ && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh )
|
||||
|
||||
|
|
|
@ -8,11 +8,11 @@ set -x -e
|
|||
source ../rust_version.sh
|
||||
set_rust_to_1671
|
||||
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_libmonero/scripts/macos/ && ./build_all.sh ) &&
|
||||
set_rust_to_1720 &&
|
||||
(cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/macos && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/macos && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libmonero/scripts/macos/ && ./build_all.sh )
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/macos && ./build_all.sh )
|
||||
|
||||
wait
|
||||
echo "Done building"
|
||||
|
|
|
@ -9,11 +9,11 @@ source ../rust_version.sh
|
|||
set_rust_to_1671
|
||||
|
||||
mkdir -p build
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/flutter_libepiccash/scripts/windows && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_liblelantus/scripts/windows && ./build_all.sh )
|
||||
(cd ../../crypto_plugins/flutter_libmonero/scripts/windows && ./build_all.sh)
|
||||
set_rust_to_1720
|
||||
(cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh ) &
|
||||
(cd ../../crypto_plugins/frostdart/scripts/windows && ./build_all.sh )
|
||||
|
||||
./build_secp256k1_wsl.sh
|
||||
|
||||
|
|
|
@ -428,21 +428,21 @@ 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<Set<String>> getSparkUsedCoinsTags({
|
||||
_i7.Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
#getSparkUnhashedUsedCoinsTags,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i7.Future<Set<String>>.value(<String>{}),
|
||||
) as _i7.Future<Set<String>>);
|
||||
returnValue: _i7.Future<List<String>>.value(<String>[]),
|
||||
) as _i7.Future<List<String>>);
|
||||
@override
|
||||
_i7.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
|
|
|
@ -78,27 +78,6 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i4.Future<Map<String, dynamic>> getSparkAnonymitySet({
|
||||
required String? groupId,
|
||||
String? blockhash = r'',
|
||||
required _i5.CryptoCurrency? cryptoCurrency,
|
||||
required bool? useOnlyCacheIfNotEmpty,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkAnonymitySet,
|
||||
[],
|
||||
{
|
||||
#groupId: groupId,
|
||||
#blockhash: blockhash,
|
||||
#cryptoCurrency: cryptoCurrency,
|
||||
#useOnlyCacheIfNotEmpty: useOnlyCacheIfNotEmpty,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
String base64ToHex(String? source) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#base64ToHex,
|
||||
|
@ -162,17 +141,6 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
returnValue: _i4.Future<List<String>>.value(<String>[]),
|
||||
) as _i4.Future<List<String>>);
|
||||
@override
|
||||
_i4.Future<Set<String>> getSparkUsedCoinsTags(
|
||||
{required _i5.CryptoCurrency? cryptoCurrency}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
[],
|
||||
{#cryptoCurrency: cryptoCurrency},
|
||||
),
|
||||
returnValue: _i4.Future<Set<String>>.value(<String>{}),
|
||||
) as _i4.Future<Set<String>>);
|
||||
@override
|
||||
_i4.Future<void> clearSharedTransactionCache(
|
||||
{required _i5.CryptoCurrency? cryptoCurrency}) =>
|
||||
(super.noSuchMethod(
|
||||
|
|
|
@ -425,21 +425,21 @@ 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<Set<String>> getSparkUsedCoinsTags({
|
||||
_i6.Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
#getSparkUnhashedUsedCoinsTags,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
|
@ -559,27 +559,6 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getSparkAnonymitySet({
|
||||
required String? groupId,
|
||||
String? blockhash = r'',
|
||||
required _i2.CryptoCurrency? cryptoCurrency,
|
||||
required bool? useOnlyCacheIfNotEmpty,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkAnonymitySet,
|
||||
[],
|
||||
{
|
||||
#groupId: groupId,
|
||||
#blockhash: blockhash,
|
||||
#cryptoCurrency: cryptoCurrency,
|
||||
#useOnlyCacheIfNotEmpty: useOnlyCacheIfNotEmpty,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
String base64ToHex(String? source) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#base64ToHex,
|
||||
|
@ -643,17 +622,6 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<Set<String>> getSparkUsedCoinsTags(
|
||||
{required _i2.CryptoCurrency? cryptoCurrency}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
[],
|
||||
{#cryptoCurrency: cryptoCurrency},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
@override
|
||||
_i6.Future<void> clearSharedTransactionCache(
|
||||
{required _i2.CryptoCurrency? cryptoCurrency}) =>
|
||||
(super.noSuchMethod(
|
||||
|
|
|
@ -425,21 +425,21 @@ 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<Set<String>> getSparkUsedCoinsTags({
|
||||
_i6.Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
#getSparkUnhashedUsedCoinsTags,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
|
@ -559,27 +559,6 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getSparkAnonymitySet({
|
||||
required String? groupId,
|
||||
String? blockhash = r'',
|
||||
required _i2.CryptoCurrency? cryptoCurrency,
|
||||
required bool? useOnlyCacheIfNotEmpty,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkAnonymitySet,
|
||||
[],
|
||||
{
|
||||
#groupId: groupId,
|
||||
#blockhash: blockhash,
|
||||
#cryptoCurrency: cryptoCurrency,
|
||||
#useOnlyCacheIfNotEmpty: useOnlyCacheIfNotEmpty,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
String base64ToHex(String? source) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#base64ToHex,
|
||||
|
@ -643,17 +622,6 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<Set<String>> getSparkUsedCoinsTags(
|
||||
{required _i2.CryptoCurrency? cryptoCurrency}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
[],
|
||||
{#cryptoCurrency: cryptoCurrency},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
@override
|
||||
_i6.Future<void> clearSharedTransactionCache(
|
||||
{required _i2.CryptoCurrency? cryptoCurrency}) =>
|
||||
(super.noSuchMethod(
|
||||
|
|
|
@ -425,21 +425,21 @@ 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<Set<String>> getSparkUsedCoinsTags({
|
||||
_i6.Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
#getSparkUnhashedUsedCoinsTags,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
|
@ -559,27 +559,6 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getSparkAnonymitySet({
|
||||
required String? groupId,
|
||||
String? blockhash = r'',
|
||||
required _i2.CryptoCurrency? cryptoCurrency,
|
||||
required bool? useOnlyCacheIfNotEmpty,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkAnonymitySet,
|
||||
[],
|
||||
{
|
||||
#groupId: groupId,
|
||||
#blockhash: blockhash,
|
||||
#cryptoCurrency: cryptoCurrency,
|
||||
#useOnlyCacheIfNotEmpty: useOnlyCacheIfNotEmpty,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
String base64ToHex(String? source) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#base64ToHex,
|
||||
|
@ -643,17 +622,6 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<Set<String>> getSparkUsedCoinsTags(
|
||||
{required _i2.CryptoCurrency? cryptoCurrency}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
[],
|
||||
{#cryptoCurrency: cryptoCurrency},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
@override
|
||||
_i6.Future<void> clearSharedTransactionCache(
|
||||
{required _i2.CryptoCurrency? cryptoCurrency}) =>
|
||||
(super.noSuchMethod(
|
||||
|
|
|
@ -425,21 +425,21 @@ 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<Set<String>> getSparkUsedCoinsTags({
|
||||
_i6.Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
#getSparkUnhashedUsedCoinsTags,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
|
@ -559,27 +559,6 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getSparkAnonymitySet({
|
||||
required String? groupId,
|
||||
String? blockhash = r'',
|
||||
required _i2.CryptoCurrency? cryptoCurrency,
|
||||
required bool? useOnlyCacheIfNotEmpty,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkAnonymitySet,
|
||||
[],
|
||||
{
|
||||
#groupId: groupId,
|
||||
#blockhash: blockhash,
|
||||
#cryptoCurrency: cryptoCurrency,
|
||||
#useOnlyCacheIfNotEmpty: useOnlyCacheIfNotEmpty,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
String base64ToHex(String? source) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#base64ToHex,
|
||||
|
@ -643,17 +622,6 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<Set<String>> getSparkUsedCoinsTags(
|
||||
{required _i2.CryptoCurrency? cryptoCurrency}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
[],
|
||||
{#cryptoCurrency: cryptoCurrency},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
@override
|
||||
_i6.Future<void> clearSharedTransactionCache(
|
||||
{required _i2.CryptoCurrency? cryptoCurrency}) =>
|
||||
(super.noSuchMethod(
|
||||
|
|
|
@ -425,21 +425,21 @@ 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<Set<String>> getSparkUsedCoinsTags({
|
||||
_i6.Future<List<String>> getSparkUnhashedUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
#getSparkUnhashedUsedCoinsTags,
|
||||
[],
|
||||
{
|
||||
#requestID: requestID,
|
||||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
|
@ -559,27 +559,6 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i6.Future<Map<String, dynamic>> getSparkAnonymitySet({
|
||||
required String? groupId,
|
||||
String? blockhash = r'',
|
||||
required _i2.CryptoCurrency? cryptoCurrency,
|
||||
required bool? useOnlyCacheIfNotEmpty,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkAnonymitySet,
|
||||
[],
|
||||
{
|
||||
#groupId: groupId,
|
||||
#blockhash: blockhash,
|
||||
#cryptoCurrency: cryptoCurrency,
|
||||
#useOnlyCacheIfNotEmpty: useOnlyCacheIfNotEmpty,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i6.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i6.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
String base64ToHex(String? source) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#base64ToHex,
|
||||
|
@ -643,17 +622,6 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
returnValue: _i6.Future<List<String>>.value(<String>[]),
|
||||
) as _i6.Future<List<String>>);
|
||||
@override
|
||||
_i6.Future<Set<String>> getSparkUsedCoinsTags(
|
||||
{required _i2.CryptoCurrency? cryptoCurrency}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
[],
|
||||
{#cryptoCurrency: cryptoCurrency},
|
||||
),
|
||||
returnValue: _i6.Future<Set<String>>.value(<String>{}),
|
||||
) as _i6.Future<Set<String>>);
|
||||
@override
|
||||
_i6.Future<void> clearSharedTransactionCache(
|
||||
{required _i2.CryptoCurrency? cryptoCurrency}) =>
|
||||
(super.noSuchMethod(
|
||||
|
|
33
test/utilities/extensions/list_test.dart
Normal file
33
test/utilities/extensions/list_test.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:stackwallet/utilities/extensions/extensions.dart';
|
||||
|
||||
void main() {
|
||||
test("Empty list", () {
|
||||
final List<int> list = [];
|
||||
expect(
|
||||
list.chunked(chunkSize: 3).isEmpty,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("No remainder", () {
|
||||
final List<int> list = [0, 1, 2, 3, 4, 5, 6, 7, 8];
|
||||
final chunked = list.chunked(chunkSize: 3);
|
||||
expect(chunked.length == 3, true);
|
||||
expect(
|
||||
chunked.map((e) => e.length == 3).reduce((v, e) => v && e),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("Some remainder", () {
|
||||
final List<int> list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
final chunked = list.chunked(chunkSize: 3);
|
||||
expect(chunked.length == 4, true);
|
||||
expect(chunked.last.length == 1, true);
|
||||
expect(
|
||||
chunked.map((e) => e.length == 3).reduce((v, e) => v && e),
|
||||
false,
|
||||
);
|
||||
});
|
||||
}
|
|
@ -13,6 +13,7 @@
|
|||
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||
#include <stack_wallet_backup/stack_wallet_backup_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
#include <window_size/window_size_plugin.h>
|
||||
|
@ -32,6 +33,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||
Sqlite3FlutterLibsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin"));
|
||||
StackWalletBackupPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("StackWalletBackupPluginCApi"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
|
|
|
@ -10,6 +10,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
isar_flutter_libs
|
||||
permission_handler_windows
|
||||
share_plus
|
||||
sqlite3_flutter_libs
|
||||
stack_wallet_backup
|
||||
url_launcher_windows
|
||||
window_size
|
||||
|
|
Loading…
Reference in a new issue