mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-22 10:34:32 +00:00
Merge pull request #713 from cypherstack/wallets_refactor_spark_integrationn
firo spark added
This commit is contained in:
commit
8a42287a77
74 changed files with 8537 additions and 2033 deletions
|
@ -53,8 +53,12 @@ class DB {
|
|||
// firo only
|
||||
String _boxNameSetCache({required Coin coin}) =>
|
||||
"${coin.name}_anonymitySetCache";
|
||||
String _boxNameSetSparkCache({required Coin coin}) =>
|
||||
"${coin.name}_anonymitySetSparkCache";
|
||||
String _boxNameUsedSerialsCache({required Coin coin}) =>
|
||||
"${coin.name}_usedSerialsCache";
|
||||
String _boxNameSparkUsedCoinsTagsCache({required Coin coin}) =>
|
||||
"${coin.name}_sparkUsedCoinsTagsCache";
|
||||
|
||||
Box<NodeModel>? _boxNodeModels;
|
||||
Box<NodeModel>? _boxPrimaryNodes;
|
||||
|
@ -75,7 +79,9 @@ class DB {
|
|||
|
||||
final Map<Coin, Box<dynamic>> _txCacheBoxes = {};
|
||||
final Map<Coin, Box<dynamic>> _setCacheBoxes = {};
|
||||
final Map<Coin, Box<dynamic>> _setSparkCacheBoxes = {};
|
||||
final Map<Coin, Box<dynamic>> _usedSerialsCacheBoxes = {};
|
||||
final Map<Coin, Box<dynamic>> _getSparkUsedCoinsTagsCacheBoxes = {};
|
||||
|
||||
// exposed for monero
|
||||
Box<xmr.WalletInfo> get moneroWalletInfoBox => _walletInfoSource!;
|
||||
|
@ -197,6 +203,15 @@ class DB {
|
|||
await Hive.openBox<dynamic>(_boxNameSetCache(coin: coin));
|
||||
}
|
||||
|
||||
Future<Box<dynamic>> getSparkAnonymitySetCacheBox(
|
||||
{required Coin coin}) async {
|
||||
if (_setSparkCacheBoxes[coin]?.isOpen != true) {
|
||||
_setSparkCacheBoxes.remove(coin);
|
||||
}
|
||||
return _setSparkCacheBoxes[coin] ??=
|
||||
await Hive.openBox<dynamic>(_boxNameSetSparkCache(coin: coin));
|
||||
}
|
||||
|
||||
Future<void> closeAnonymitySetCacheBox({required Coin coin}) async {
|
||||
await _setCacheBoxes[coin]?.close();
|
||||
}
|
||||
|
@ -209,6 +224,16 @@ class DB {
|
|||
await Hive.openBox<dynamic>(_boxNameUsedSerialsCache(coin: coin));
|
||||
}
|
||||
|
||||
Future<Box<dynamic>> getSparkUsedCoinsTagsCacheBox(
|
||||
{required Coin coin}) async {
|
||||
if (_getSparkUsedCoinsTagsCacheBoxes[coin]?.isOpen != true) {
|
||||
_getSparkUsedCoinsTagsCacheBoxes.remove(coin);
|
||||
}
|
||||
return _getSparkUsedCoinsTagsCacheBoxes[coin] ??=
|
||||
await Hive.openBox<dynamic>(
|
||||
_boxNameSparkUsedCoinsTagsCache(coin: coin));
|
||||
}
|
||||
|
||||
Future<void> closeUsedSerialsCacheBox({required Coin coin}) async {
|
||||
await _usedSerialsCacheBoxes[coin]?.close();
|
||||
}
|
||||
|
@ -216,9 +241,12 @@ class DB {
|
|||
/// Clear all cached transactions for the specified coin
|
||||
Future<void> clearSharedTransactionCache({required Coin coin}) async {
|
||||
await deleteAll<dynamic>(boxName: _boxNameTxCache(coin: coin));
|
||||
if (coin == Coin.firo) {
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
await deleteAll<dynamic>(boxName: _boxNameSetCache(coin: coin));
|
||||
await deleteAll<dynamic>(boxName: _boxNameSetSparkCache(coin: coin));
|
||||
await deleteAll<dynamic>(boxName: _boxNameUsedSerialsCache(coin: coin));
|
||||
await deleteAll<dynamic>(
|
||||
boxName: _boxNameSparkUsedCoinsTagsCache(coin: coin));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import 'package:stackwallet/models/isar/stack_theme.dart';
|
|||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||
import 'package:stackwallet/wallets/isar/models/spark_coin.dart';
|
||||
import 'package:stackwallet/wallets/isar/models/wallet_info.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
|
@ -61,6 +62,7 @@ class MainDB {
|
|||
LelantusCoinSchema,
|
||||
WalletInfoSchema,
|
||||
TransactionV2Schema,
|
||||
SparkCoinSchema,
|
||||
],
|
||||
directory: (await StackFileSystem.applicationIsarDirectory()).path,
|
||||
// inspector: kDebugMode,
|
||||
|
@ -482,6 +484,12 @@ class MainDB {
|
|||
// .findAll();
|
||||
// await isar.lelantusCoins.deleteAll(lelantusCoinIds);
|
||||
// }
|
||||
|
||||
// spark coins
|
||||
await isar.sparkCoins
|
||||
.where()
|
||||
.walletIdEqualToAnyLTagHash(walletId)
|
||||
.deleteAll();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'package:stackwallet/wallets/wallet/supporting/epiccash_wallet_info_exten
|
|||
Future<void> migrateWalletsToIsar({
|
||||
required SecureStorageInterface secureStore,
|
||||
}) async {
|
||||
await MainDB.instance.initMainDB();
|
||||
final allWalletsBox = await Hive.openBox<dynamic>(DB.boxNameAllWalletsData);
|
||||
|
||||
final names = DB.instance
|
||||
|
|
|
@ -107,6 +107,63 @@ class CachedElectrumXClient {
|
|||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getSparkAnonymitySet({
|
||||
required String groupId,
|
||||
String blockhash = "",
|
||||
required Coin coin,
|
||||
}) async {
|
||||
try {
|
||||
final box = await DB.instance.getSparkAnonymitySetCacheBox(coin: coin);
|
||||
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);
|
||||
}
|
||||
|
||||
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 ${coin.name} 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'))
|
||||
|
@ -136,6 +193,7 @@ class CachedElectrumXClient {
|
|||
|
||||
result.remove("hex");
|
||||
result.remove("lelantusData");
|
||||
result.remove("sparkData");
|
||||
|
||||
if (result["confirmations"] != null &&
|
||||
result["confirmations"] as int > minCacheConfirms) {
|
||||
|
@ -198,14 +256,62 @@ class CachedElectrumXClient {
|
|||
return resultingList;
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to process CachedElectrumX.getTransaction(): $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
"Failed to process CachedElectrumX.getUsedCoinSerials(): $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Set<String>> getSparkUsedCoinsTags({
|
||||
required Coin coin,
|
||||
}) async {
|
||||
try {
|
||||
final box = await DB.instance.getSparkUsedCoinsTagsCacheBox(coin: coin);
|
||||
|
||||
final _list = box.get("tags") as List?;
|
||||
|
||||
Set<String> cachedTags =
|
||||
_list == null ? {} : List<String>.from(_list).toSet();
|
||||
|
||||
final startNumber = max(
|
||||
0,
|
||||
cachedTags.length - 100, // 100 being some arbitrary buffer
|
||||
);
|
||||
|
||||
final tags = await electrumXClient.getSparkUsedCoinsTags(
|
||||
startNumber: startNumber,
|
||||
);
|
||||
|
||||
// final newSerials = List<String>.from(serials["serials"] as List)
|
||||
// .map((e) => !isHexadecimal(e) ? base64ToHex(e) : e)
|
||||
// .toSet();
|
||||
|
||||
// ensure we are getting some overlap so we know we are not missing any
|
||||
if (cachedTags.isNotEmpty && tags.isNotEmpty) {
|
||||
assert(cachedTags.intersection(tags).isNotEmpty);
|
||||
}
|
||||
|
||||
cachedTags.addAll(tags);
|
||||
|
||||
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 Coin coin}) async {
|
||||
await DB.instance.clearSharedTransactionCache(coin: coin);
|
||||
await DB.instance.closeAnonymitySetCacheBox(coin: coin);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ import 'dart:io';
|
|||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:decimal/decimal.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 'package:stackwallet/electrumx_rpc/rpc.dart';
|
||||
import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart';
|
||||
|
@ -467,9 +469,9 @@ class ElectrumXClient {
|
|||
/// and the binary header as a hexadecimal string.
|
||||
/// Ex:
|
||||
/// {
|
||||
// "height": 520481,
|
||||
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
|
||||
// }
|
||||
/// "height": 520481,
|
||||
/// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
|
||||
/// }
|
||||
Future<Map<String, dynamic>> getBlockHeadTip({String? requestID}) async {
|
||||
try {
|
||||
final response = await request(
|
||||
|
@ -493,15 +495,15 @@ class ElectrumXClient {
|
|||
///
|
||||
/// Returns a map with server information
|
||||
/// Ex:
|
||||
// {
|
||||
// "genesis_hash": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
|
||||
// "hosts": {"14.3.140.101": {"tcp_port": 51001, "ssl_port": 51002}},
|
||||
// "protocol_max": "1.0",
|
||||
// "protocol_min": "1.0",
|
||||
// "pruning": null,
|
||||
// "server_version": "ElectrumX 1.0.17",
|
||||
// "hash_function": "sha256"
|
||||
// }
|
||||
/// {
|
||||
/// "genesis_hash": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
|
||||
/// "hosts": {"14.3.140.101": {"tcp_port": 51001, "ssl_port": 51002}},
|
||||
/// "protocol_max": "1.0",
|
||||
/// "protocol_min": "1.0",
|
||||
/// "pruning": null,
|
||||
/// "server_version": "ElectrumX 1.0.17",
|
||||
/// "hash_function": "sha256"
|
||||
/// }
|
||||
Future<Map<String, dynamic>> getServerFeatures({String? requestID}) async {
|
||||
try {
|
||||
final response = await request(
|
||||
|
@ -567,15 +569,15 @@ class ElectrumXClient {
|
|||
/// Returns a list of maps that contain the tx_hash and height of the tx.
|
||||
/// Ex:
|
||||
/// [
|
||||
// {
|
||||
// "height": 200004,
|
||||
// "tx_hash": "acc3758bd2a26f869fcc67d48ff30b96464d476bca82c1cd6656e7d506816412"
|
||||
// },
|
||||
// {
|
||||
// "height": 215008,
|
||||
// "tx_hash": "f3e1bf48975b8d6060a9de8884296abb80be618dc00ae3cb2f6cee3085e09403"
|
||||
// }
|
||||
// ]
|
||||
/// {
|
||||
/// "height": 200004,
|
||||
/// "tx_hash": "acc3758bd2a26f869fcc67d48ff30b96464d476bca82c1cd6656e7d506816412"
|
||||
/// },
|
||||
/// {
|
||||
/// "height": 215008,
|
||||
/// "tx_hash": "f3e1bf48975b8d6060a9de8884296abb80be618dc00ae3cb2f6cee3085e09403"
|
||||
/// }
|
||||
/// ]
|
||||
Future<List<Map<String, dynamic>>> getHistory({
|
||||
required String scripthash,
|
||||
String? requestID,
|
||||
|
@ -627,19 +629,19 @@ class ElectrumXClient {
|
|||
/// Returns a list of maps.
|
||||
/// Ex:
|
||||
/// [
|
||||
// {
|
||||
// "tx_pos": 0,
|
||||
// "value": 45318048,
|
||||
// "tx_hash": "9f2c45a12db0144909b5db269415f7319179105982ac70ed80d76ea79d923ebf",
|
||||
// "height": 437146
|
||||
// },
|
||||
// {
|
||||
// "tx_pos": 0,
|
||||
// "value": 919195,
|
||||
// "tx_hash": "3d2290c93436a3e964cfc2f0950174d8847b1fbe3946432c4784e168da0f019f",
|
||||
// "height": 441696
|
||||
// }
|
||||
// ]
|
||||
/// {
|
||||
/// "tx_pos": 0,
|
||||
/// "value": 45318048,
|
||||
/// "tx_hash": "9f2c45a12db0144909b5db269415f7319179105982ac70ed80d76ea79d923ebf",
|
||||
/// "height": 437146
|
||||
/// },
|
||||
/// {
|
||||
/// "tx_pos": 0,
|
||||
/// "value": 919195,
|
||||
/// "tx_hash": "3d2290c93436a3e964cfc2f0950174d8847b1fbe3946432c4784e168da0f019f",
|
||||
/// "height": 441696
|
||||
/// }
|
||||
/// ]
|
||||
Future<List<Map<String, dynamic>>> getUTXOs({
|
||||
required String scripthash,
|
||||
String? requestID,
|
||||
|
@ -881,7 +883,7 @@ class ElectrumXClient {
|
|||
///
|
||||
/// Returns blockHash (last block hash),
|
||||
/// setHash (hash of current set)
|
||||
/// and mints (the list of pairs serialized coin and tx hash)
|
||||
/// and coins (the list of pairs serialized coin and tx hash)
|
||||
Future<Map<String, dynamic>> getSparkAnonymitySet({
|
||||
String coinGroupId = "1",
|
||||
String startBlockHash = "",
|
||||
|
@ -908,7 +910,7 @@ class ElectrumXClient {
|
|||
|
||||
/// Takes [startNumber], if it is 0, we get the full set,
|
||||
/// otherwise the used tags after that number
|
||||
Future<Map<String, dynamic>> getSparkUsedCoinsTags({
|
||||
Future<Set<String>> getSparkUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int startNumber,
|
||||
}) async {
|
||||
|
@ -921,7 +923,9 @@ class ElectrumXClient {
|
|||
],
|
||||
requestTimeout: const Duration(minutes: 2),
|
||||
);
|
||||
return Map<String, dynamic>.from(response["result"] as Map);
|
||||
final map = Map<String, dynamic>.from(response["result"] as Map);
|
||||
final set = Set<String>.from(map["tags"] as List);
|
||||
return await compute(_ffiHashTagsComputeWrapper, set);
|
||||
} catch (e) {
|
||||
Logging.instance.log(e, level: LogLevel.Error);
|
||||
rethrow;
|
||||
|
@ -984,8 +988,8 @@ class ElectrumXClient {
|
|||
/// Returns a map with the kay "rate" that corresponds to the free rate in satoshis
|
||||
/// Ex:
|
||||
/// {
|
||||
// "rate": 1000,
|
||||
// }
|
||||
/// "rate": 1000,
|
||||
/// }
|
||||
Future<Map<String, dynamic>> getFeeRate({String? requestID}) async {
|
||||
try {
|
||||
final response = await request(
|
||||
|
@ -1035,3 +1039,7 @@ class ElectrumXClient {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> _ffiHashTagsComputeWrapper(Set<String> base64Tags) {
|
||||
return LibSpark.hashTags(base64Tags: base64Tags);
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ import 'package:stackwallet/utilities/logger.dart';
|
|||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:stackwallet/utilities/stack_file_system.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/wallets/isar/providers/all_wallets_info_provider.dart';
|
||||
import 'package:stackwallet/widgets/crypto_notifications.dart';
|
||||
import 'package:window_size/window_size.dart';
|
||||
|
||||
|
@ -747,7 +748,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
|
|||
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
// FlutterNativeSplash.remove();
|
||||
if (ref.read(pWallets).hasWallets ||
|
||||
if (ref.read(pAllWalletsInfo).isNotEmpty ||
|
||||
ref.read(prefsChangeNotifierProvider).hasPin) {
|
||||
// return HomeView();
|
||||
|
||||
|
|
|
@ -252,5 +252,7 @@ enum TransactionSubType {
|
|||
mint, // firo specific
|
||||
join, // firo specific
|
||||
ethToken, // eth token
|
||||
cashFusion;
|
||||
cashFusion,
|
||||
sparkMint, // firo specific
|
||||
sparkSpend; // firo specific
|
||||
}
|
||||
|
|
|
@ -365,6 +365,8 @@ const _TransactionsubTypeEnumValueMap = {
|
|||
'join': 3,
|
||||
'ethToken': 4,
|
||||
'cashFusion': 5,
|
||||
'sparkMint': 6,
|
||||
'sparkSpend': 7,
|
||||
};
|
||||
const _TransactionsubTypeValueEnumMap = {
|
||||
0: TransactionSubType.none,
|
||||
|
@ -373,6 +375,8 @@ const _TransactionsubTypeValueEnumMap = {
|
|||
3: TransactionSubType.join,
|
||||
4: TransactionSubType.ethToken,
|
||||
5: TransactionSubType.cashFusion,
|
||||
6: TransactionSubType.sparkMint,
|
||||
7: TransactionSubType.sparkSpend,
|
||||
};
|
||||
const _TransactiontypeEnumValueMap = {
|
||||
'outgoing': 0,
|
||||
|
|
|
@ -46,7 +46,7 @@ class OutputV2 {
|
|||
Map<String, dynamic> json, {
|
||||
required bool walletOwns,
|
||||
required int decimalPlaces,
|
||||
bool isECashFullAmountNotSats = false,
|
||||
bool isFullAmountNotSats = false,
|
||||
}) {
|
||||
try {
|
||||
List<String> addresses = [];
|
||||
|
@ -61,9 +61,11 @@ class OutputV2 {
|
|||
|
||||
return OutputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptPubKeyHex: json["scriptPubKey"]["hex"] as String,
|
||||
valueStringSats: parseOutputAmountString(json["value"].toString(),
|
||||
valueStringSats: parseOutputAmountString(
|
||||
json["value"].toString(),
|
||||
decimalPlaces: decimalPlaces,
|
||||
isECashFullAmountNotSats: isECashFullAmountNotSats),
|
||||
isFullAmountNotSats: isFullAmountNotSats,
|
||||
),
|
||||
addresses: addresses,
|
||||
walletOwns: walletOwns,
|
||||
);
|
||||
|
@ -75,7 +77,7 @@ class OutputV2 {
|
|||
static String parseOutputAmountString(
|
||||
String amount, {
|
||||
required int decimalPlaces,
|
||||
bool isECashFullAmountNotSats = false,
|
||||
bool isFullAmountNotSats = false,
|
||||
}) {
|
||||
final temp = Decimal.parse(amount);
|
||||
if (temp < Decimal.zero) {
|
||||
|
@ -83,7 +85,7 @@ class OutputV2 {
|
|||
}
|
||||
|
||||
final String valueStringSats;
|
||||
if (isECashFullAmountNotSats) {
|
||||
if (isFullAmountNotSats) {
|
||||
valueStringSats = temp.shift(decimalPlaces).toBigInt().toString();
|
||||
} else if (temp.isInteger) {
|
||||
valueStringSats = temp.toString();
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:isar/isar.dart';
|
||||
|
@ -6,6 +7,8 @@ import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart'
|
|||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/extensions/extensions.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
|
||||
part 'transaction_v2.g.dart';
|
||||
|
||||
|
@ -37,6 +40,8 @@ class TransactionV2 {
|
|||
@enumerated
|
||||
final TransactionSubType subType;
|
||||
|
||||
final String? otherData;
|
||||
|
||||
TransactionV2({
|
||||
required this.walletId,
|
||||
required this.blockHash,
|
||||
|
@ -49,6 +54,7 @@ class TransactionV2 {
|
|||
required this.version,
|
||||
required this.type,
|
||||
required this.subType,
|
||||
required this.otherData,
|
||||
});
|
||||
|
||||
int getConfirmations(int currentChainHeight) {
|
||||
|
@ -71,7 +77,7 @@ class TransactionV2 {
|
|||
return Amount(rawValue: inSum - outSum, fractionDigits: coin.decimals);
|
||||
}
|
||||
|
||||
Amount getAmountReceivedThisWallet({required Coin coin}) {
|
||||
Amount getAmountReceivedInThisWallet({required Coin coin}) {
|
||||
final outSum = outputs
|
||||
.where((e) => e.walletOwns)
|
||||
.fold(BigInt.zero, (p, e) => p + e.value);
|
||||
|
@ -79,12 +85,28 @@ class TransactionV2 {
|
|||
return Amount(rawValue: outSum, fractionDigits: coin.decimals);
|
||||
}
|
||||
|
||||
Amount getAmountSparkSelfMinted({required Coin coin}) {
|
||||
final outSum = outputs.where((e) {
|
||||
final op = e.scriptPubKeyHex.substring(0, 2).toUint8ListFromHex.first;
|
||||
return e.walletOwns && (op == OP_SPARKMINT);
|
||||
}).fold(BigInt.zero, (p, e) => p + e.value);
|
||||
|
||||
return Amount(rawValue: outSum, fractionDigits: coin.decimals);
|
||||
}
|
||||
|
||||
Amount getAmountSentFromThisWallet({required Coin coin}) {
|
||||
final inSum = inputs
|
||||
.where((e) => e.walletOwns)
|
||||
.fold(BigInt.zero, (p, e) => p + e.value);
|
||||
|
||||
return Amount(rawValue: inSum, fractionDigits: coin.decimals);
|
||||
return Amount(
|
||||
rawValue: inSum,
|
||||
fractionDigits: coin.decimals,
|
||||
) -
|
||||
getAmountReceivedInThisWallet(
|
||||
coin: coin,
|
||||
) -
|
||||
getFee(coin: coin);
|
||||
}
|
||||
|
||||
Set<String> associatedAddresses() => {
|
||||
|
@ -92,6 +114,82 @@ class TransactionV2 {
|
|||
...outputs.map((e) => e.addresses).expand((e) => e),
|
||||
};
|
||||
|
||||
Amount? getAnonFee() {
|
||||
try {
|
||||
final map = jsonDecode(otherData!) as Map;
|
||||
return Amount.fromSerializedJsonString(map["anonFees"] as String);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String statusLabel({
|
||||
required int currentChainHeight,
|
||||
required int minConfirms,
|
||||
}) {
|
||||
if (subType == TransactionSubType.cashFusion ||
|
||||
subType == TransactionSubType.mint ||
|
||||
(subType == TransactionSubType.sparkMint &&
|
||||
type == TransactionType.sentToSelf)) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms)) {
|
||||
return "Anonymized";
|
||||
} else {
|
||||
return "Anonymizing";
|
||||
}
|
||||
}
|
||||
|
||||
// if (coin == Coin.epicCash) {
|
||||
// if (_transaction.isCancelled) {
|
||||
// return "Cancelled";
|
||||
// } else if (type == TransactionType.incoming) {
|
||||
// if (isConfirmed(height, minConfirms)) {
|
||||
// return "Received";
|
||||
// } else {
|
||||
// if (_transaction.numberOfMessages == 1) {
|
||||
// return "Receiving (waiting for sender)";
|
||||
// } else if ((_transaction.numberOfMessages ?? 0) > 1) {
|
||||
// return "Receiving (waiting for confirmations)"; // TODO test if the sender still has to open again after the receiver has 2 messages present, ie. sender->receiver->sender->node (yes) vs. sender->receiver->node (no)
|
||||
// } else {
|
||||
// return "Receiving";
|
||||
// }
|
||||
// }
|
||||
// } else if (type == TransactionType.outgoing) {
|
||||
// if (isConfirmed(height, minConfirms)) {
|
||||
// return "Sent (confirmed)";
|
||||
// } else {
|
||||
// if (_transaction.numberOfMessages == 1) {
|
||||
// return "Sending (waiting for receiver)";
|
||||
// } else if ((_transaction.numberOfMessages ?? 0) > 1) {
|
||||
// return "Sending (waiting for confirmations)";
|
||||
// } else {
|
||||
// return "Sending";
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if (type == TransactionType.incoming) {
|
||||
// if (_transaction.isMinting) {
|
||||
// return "Minting";
|
||||
// } else
|
||||
if (isConfirmed(currentChainHeight, minConfirms)) {
|
||||
return "Received";
|
||||
} else {
|
||||
return "Receiving";
|
||||
}
|
||||
} else if (type == TransactionType.outgoing) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms)) {
|
||||
return "Sent";
|
||||
} else {
|
||||
return "Sending";
|
||||
}
|
||||
} else if (type == TransactionType.sentToSelf) {
|
||||
return "Sent to self";
|
||||
} else {
|
||||
return type.name;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TransactionV2(\n'
|
||||
|
@ -106,6 +204,7 @@ class TransactionV2 {
|
|||
' version: $version,\n'
|
||||
' inputs: $inputs,\n'
|
||||
' outputs: $outputs,\n'
|
||||
' otherData: $otherData,\n'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,41 +38,46 @@ const TransactionV2Schema = CollectionSchema(
|
|||
type: IsarType.objectList,
|
||||
target: r'InputV2',
|
||||
),
|
||||
r'outputs': PropertySchema(
|
||||
r'otherData': PropertySchema(
|
||||
id: 4,
|
||||
name: r'otherData',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'outputs': PropertySchema(
|
||||
id: 5,
|
||||
name: r'outputs',
|
||||
type: IsarType.objectList,
|
||||
target: r'OutputV2',
|
||||
),
|
||||
r'subType': PropertySchema(
|
||||
id: 5,
|
||||
id: 6,
|
||||
name: r'subType',
|
||||
type: IsarType.byte,
|
||||
enumMap: _TransactionV2subTypeEnumValueMap,
|
||||
),
|
||||
r'timestamp': PropertySchema(
|
||||
id: 6,
|
||||
id: 7,
|
||||
name: r'timestamp',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'txid': PropertySchema(
|
||||
id: 7,
|
||||
id: 8,
|
||||
name: r'txid',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'type': PropertySchema(
|
||||
id: 8,
|
||||
id: 9,
|
||||
name: r'type',
|
||||
type: IsarType.byte,
|
||||
enumMap: _TransactionV2typeEnumValueMap,
|
||||
),
|
||||
r'version': PropertySchema(
|
||||
id: 9,
|
||||
id: 10,
|
||||
name: r'version',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'walletId': PropertySchema(
|
||||
id: 10,
|
||||
id: 11,
|
||||
name: r'walletId',
|
||||
type: IsarType.string,
|
||||
)
|
||||
|
@ -161,6 +166,12 @@ int _transactionV2EstimateSize(
|
|||
bytesCount += InputV2Schema.estimateSize(value, offsets, allOffsets);
|
||||
}
|
||||
}
|
||||
{
|
||||
final value = object.otherData;
|
||||
if (value != null) {
|
||||
bytesCount += 3 + value.length * 3;
|
||||
}
|
||||
}
|
||||
bytesCount += 3 + object.outputs.length * 3;
|
||||
{
|
||||
final offsets = allOffsets[OutputV2]!;
|
||||
|
@ -189,18 +200,19 @@ void _transactionV2Serialize(
|
|||
InputV2Schema.serialize,
|
||||
object.inputs,
|
||||
);
|
||||
writer.writeString(offsets[4], object.otherData);
|
||||
writer.writeObjectList<OutputV2>(
|
||||
offsets[4],
|
||||
offsets[5],
|
||||
allOffsets,
|
||||
OutputV2Schema.serialize,
|
||||
object.outputs,
|
||||
);
|
||||
writer.writeByte(offsets[5], object.subType.index);
|
||||
writer.writeLong(offsets[6], object.timestamp);
|
||||
writer.writeString(offsets[7], object.txid);
|
||||
writer.writeByte(offsets[8], object.type.index);
|
||||
writer.writeLong(offsets[9], object.version);
|
||||
writer.writeString(offsets[10], object.walletId);
|
||||
writer.writeByte(offsets[6], object.subType.index);
|
||||
writer.writeLong(offsets[7], object.timestamp);
|
||||
writer.writeString(offsets[8], object.txid);
|
||||
writer.writeByte(offsets[9], object.type.index);
|
||||
writer.writeLong(offsets[10], object.version);
|
||||
writer.writeString(offsets[11], object.walletId);
|
||||
}
|
||||
|
||||
TransactionV2 _transactionV2Deserialize(
|
||||
|
@ -220,22 +232,23 @@ TransactionV2 _transactionV2Deserialize(
|
|||
InputV2(),
|
||||
) ??
|
||||
[],
|
||||
otherData: reader.readStringOrNull(offsets[4]),
|
||||
outputs: reader.readObjectList<OutputV2>(
|
||||
offsets[4],
|
||||
offsets[5],
|
||||
OutputV2Schema.deserialize,
|
||||
allOffsets,
|
||||
OutputV2(),
|
||||
) ??
|
||||
[],
|
||||
subType:
|
||||
_TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[5])] ??
|
||||
_TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[6])] ??
|
||||
TransactionSubType.none,
|
||||
timestamp: reader.readLong(offsets[6]),
|
||||
txid: reader.readString(offsets[7]),
|
||||
type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[8])] ??
|
||||
timestamp: reader.readLong(offsets[7]),
|
||||
txid: reader.readString(offsets[8]),
|
||||
type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[9])] ??
|
||||
TransactionType.outgoing,
|
||||
version: reader.readLong(offsets[9]),
|
||||
walletId: reader.readString(offsets[10]),
|
||||
version: reader.readLong(offsets[10]),
|
||||
walletId: reader.readString(offsets[11]),
|
||||
);
|
||||
object.id = id;
|
||||
return object;
|
||||
|
@ -263,6 +276,8 @@ P _transactionV2DeserializeProp<P>(
|
|||
) ??
|
||||
[]) as P;
|
||||
case 4:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 5:
|
||||
return (reader.readObjectList<OutputV2>(
|
||||
offset,
|
||||
OutputV2Schema.deserialize,
|
||||
|
@ -270,20 +285,20 @@ P _transactionV2DeserializeProp<P>(
|
|||
OutputV2(),
|
||||
) ??
|
||||
[]) as P;
|
||||
case 5:
|
||||
case 6:
|
||||
return (_TransactionV2subTypeValueEnumMap[
|
||||
reader.readByteOrNull(offset)] ??
|
||||
TransactionSubType.none) as P;
|
||||
case 6:
|
||||
return (reader.readLong(offset)) as P;
|
||||
case 7:
|
||||
return (reader.readString(offset)) as P;
|
||||
return (reader.readLong(offset)) as P;
|
||||
case 8:
|
||||
return (reader.readString(offset)) as P;
|
||||
case 9:
|
||||
return (_TransactionV2typeValueEnumMap[reader.readByteOrNull(offset)] ??
|
||||
TransactionType.outgoing) as P;
|
||||
case 9:
|
||||
return (reader.readLong(offset)) as P;
|
||||
case 10:
|
||||
return (reader.readLong(offset)) as P;
|
||||
case 11:
|
||||
return (reader.readString(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
|
@ -297,6 +312,8 @@ const _TransactionV2subTypeEnumValueMap = {
|
|||
'join': 3,
|
||||
'ethToken': 4,
|
||||
'cashFusion': 5,
|
||||
'sparkMint': 6,
|
||||
'sparkSpend': 7,
|
||||
};
|
||||
const _TransactionV2subTypeValueEnumMap = {
|
||||
0: TransactionSubType.none,
|
||||
|
@ -305,6 +322,8 @@ const _TransactionV2subTypeValueEnumMap = {
|
|||
3: TransactionSubType.join,
|
||||
4: TransactionSubType.ethToken,
|
||||
5: TransactionSubType.cashFusion,
|
||||
6: TransactionSubType.sparkMint,
|
||||
7: TransactionSubType.sparkSpend,
|
||||
};
|
||||
const _TransactionV2typeEnumValueMap = {
|
||||
'outgoing': 0,
|
||||
|
@ -1244,6 +1263,160 @@ extension TransactionV2QueryFilter
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||
otherDataIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'otherData',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||
otherDataIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'otherData',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||
otherDataEqualTo(
|
||||
String? value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'otherData',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||
otherDataGreaterThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'otherData',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||
otherDataLessThan(
|
||||
String? value, {
|
||||
bool include = false,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'otherData',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||
otherDataBetween(
|
||||
String? lower,
|
||||
String? upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'otherData',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||
otherDataStartsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.startsWith(
|
||||
property: r'otherData',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||
otherDataEndsWith(
|
||||
String value, {
|
||||
bool caseSensitive = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.endsWith(
|
||||
property: r'otherData',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||
otherDataContains(String value, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.contains(
|
||||
property: r'otherData',
|
||||
value: value,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||
otherDataMatches(String pattern, {bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.matches(
|
||||
property: r'otherData',
|
||||
wildcard: pattern,
|
||||
caseSensitive: caseSensitive,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||
otherDataIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'otherData',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||
otherDataIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
property: r'otherData',
|
||||
value: '',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
|
||||
outputsLengthEqualTo(int length) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
@ -1887,6 +2060,19 @@ extension TransactionV2QuerySortBy
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortByOtherData() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'otherData', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy>
|
||||
sortByOtherDataDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'otherData', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortBySubType() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'subType', Sort.asc);
|
||||
|
@ -2013,6 +2199,19 @@ extension TransactionV2QuerySortThenBy
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenByOtherData() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'otherData', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy>
|
||||
thenByOtherDataDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'otherData', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenBySubType() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'subType', Sort.asc);
|
||||
|
@ -2110,6 +2309,13 @@ extension TransactionV2QueryWhereDistinct
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QDistinct> distinctByOtherData(
|
||||
{bool caseSensitive = true}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'otherData', caseSensitive: caseSensitive);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, TransactionV2, QDistinct> distinctBySubType() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'subType');
|
||||
|
@ -2182,6 +2388,12 @@ extension TransactionV2QueryProperty
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, String?, QQueryOperations> otherDataProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'otherData');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<TransactionV2, List<OutputV2>, QQueryOperations>
|
||||
outputsProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
|
|
@ -126,7 +126,7 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
|
|||
void initState() {
|
||||
_searchFieldController = TextEditingController();
|
||||
_searchFocusNode = FocusNode();
|
||||
_coinsTestnet.remove(Coin.firoTestNet);
|
||||
// _coinsTestnet.remove(Coin.firoTestNet);
|
||||
if (Platform.isWindows) {
|
||||
_coins.remove(Coin.monero);
|
||||
_coins.remove(Coin.wownero);
|
||||
|
|
|
@ -352,6 +352,16 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
|
|||
data: address.derivationPath!.value,
|
||||
button: Container(),
|
||||
),
|
||||
if (address.type == AddressType.spark)
|
||||
const _Div(
|
||||
height: 12,
|
||||
),
|
||||
if (address.type == AddressType.spark)
|
||||
_Item(
|
||||
title: "Diversifier",
|
||||
data: address.derivationIndex.toString(),
|
||||
button: Container(),
|
||||
),
|
||||
const _Div(
|
||||
height: 12,
|
||||
),
|
||||
|
|
|
@ -10,15 +10,18 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/receive_view/addresses/wallet_addresses_view.dart';
|
||||
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
|
||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
|
@ -30,7 +33,9 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
|||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
|
||||
|
@ -58,6 +63,11 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
late final Coin coin;
|
||||
late final String walletId;
|
||||
late final ClipboardInterface clipboard;
|
||||
late final bool supportsSpark;
|
||||
|
||||
String? _sparkAddress;
|
||||
String? _qrcodeContent;
|
||||
bool _showSparkAddress = true;
|
||||
|
||||
Future<void> generateNewAddress() async {
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
|
@ -96,23 +106,106 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> generateNewSparkAddress() async {
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
if (wallet is SparkInterface) {
|
||||
bool shouldPop = false;
|
||||
unawaited(
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => shouldPop,
|
||||
child: Container(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.overlay
|
||||
.withOpacity(0.5),
|
||||
child: const CustomLoadingOverlay(
|
||||
message: "Generating address",
|
||||
eventBus: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
final address = await wallet.generateNextSparkAddress();
|
||||
await ref.read(mainDBProvider).isar.writeTxn(() async {
|
||||
await ref.read(mainDBProvider).isar.addresses.put(address);
|
||||
});
|
||||
|
||||
shouldPop = true;
|
||||
|
||||
if (mounted) {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
if (_sparkAddress != address.value) {
|
||||
setState(() {
|
||||
_sparkAddress = address.value;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StreamSubscription<Address?>? _streamSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
walletId = widget.walletId;
|
||||
coin = ref.read(pWalletCoin(walletId));
|
||||
clipboard = widget.clipboard;
|
||||
supportsSpark = ref.read(pWallets).getWallet(walletId) is SparkInterface;
|
||||
|
||||
if (supportsSpark) {
|
||||
_streamSub = ref
|
||||
.read(mainDBProvider)
|
||||
.isar
|
||||
.addresses
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.typeEqualTo(AddressType.spark)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()
|
||||
.asStream()
|
||||
.listen((event) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_sparkAddress = event?.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_streamSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
final receivingAddress = ref.watch(pWalletReceivingAddress(walletId));
|
||||
|
||||
final ticker = widget.tokenContract?.symbol ?? coin.ticker;
|
||||
|
||||
if (supportsSpark) {
|
||||
if (_showSparkAddress) {
|
||||
_qrcodeContent = _sparkAddress;
|
||||
} else {
|
||||
_qrcodeContent = ref.watch(pWalletReceivingAddress(walletId));
|
||||
}
|
||||
} else {
|
||||
_qrcodeContent = ref.watch(pWalletReceivingAddress(walletId));
|
||||
}
|
||||
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
|
@ -225,11 +318,166 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
ConditionalParent(
|
||||
condition: supportsSpark,
|
||||
builder: (child) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<bool>(
|
||||
value: _showSparkAddress,
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: true,
|
||||
child: Text(
|
||||
"Spark address",
|
||||
style: STextStyles.desktopTextMedium(context),
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: false,
|
||||
child: Text(
|
||||
"Transparent address",
|
||||
style: STextStyles.desktopTextMedium(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value is bool && value != _showSparkAddress) {
|
||||
setState(() {
|
||||
_showSparkAddress = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
isExpanded: true,
|
||||
iconStyleData: IconStyleData(
|
||||
icon: Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 12,
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconRight,
|
||||
),
|
||||
),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
offset: const Offset(0, -10),
|
||||
elevation: 0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
if (_showSparkAddress)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
clipboard.setData(
|
||||
ClipboardData(text: _sparkAddress ?? "Error"),
|
||||
);
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
iconAsset: Assets.svg.copy,
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.backgroundAppBar,
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
child: RoundedWhiteContainer(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"Your ${coin.ticker} SPARK address",
|
||||
style:
|
||||
STextStyles.itemSubtitle(context),
|
||||
),
|
||||
const Spacer(),
|
||||
Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
width: 15,
|
||||
height: 15,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.infoItemIcons,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
Text(
|
||||
"Copy",
|
||||
style: STextStyles.link2(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
_sparkAddress ?? "Error",
|
||||
style: STextStyles
|
||||
.desktopTextExtraExtraSmall(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!_showSparkAddress) child,
|
||||
],
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
HapticFeedback.lightImpact();
|
||||
clipboard.setData(
|
||||
ClipboardData(text: receivingAddress),
|
||||
ClipboardData(
|
||||
text:
|
||||
ref.watch(pWalletReceivingAddress(walletId))),
|
||||
);
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
|
@ -276,7 +524,8 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
receivingAddress,
|
||||
ref.watch(
|
||||
pWalletReceivingAddress(walletId)),
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
),
|
||||
|
@ -286,25 +535,22 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (coin != Coin.epicCash &&
|
||||
coin != Coin.ethereum &&
|
||||
coin != Coin.banano &&
|
||||
coin != Coin.nano &&
|
||||
coin != Coin.stellar &&
|
||||
coin != Coin.stellarTestnet &&
|
||||
coin != Coin.tezos)
|
||||
),
|
||||
if (ref.watch(pWallets
|
||||
.select((value) => value.getWallet(walletId)))
|
||||
is MultiAddressInterface ||
|
||||
supportsSpark)
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
if (coin != Coin.epicCash &&
|
||||
coin != Coin.ethereum &&
|
||||
coin != Coin.banano &&
|
||||
coin != Coin.nano &&
|
||||
coin != Coin.stellar &&
|
||||
coin != Coin.stellarTestnet &&
|
||||
coin != Coin.tezos)
|
||||
if (ref.watch(pWallets
|
||||
.select((value) => value.getWallet(walletId)))
|
||||
is MultiAddressInterface ||
|
||||
supportsSpark)
|
||||
TextButton(
|
||||
onPressed: generateNewAddress,
|
||||
onPressed: supportsSpark && _showSparkAddress
|
||||
? generateNewSparkAddress
|
||||
: generateNewAddress,
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getSecondaryEnabledButtonStyle(context),
|
||||
|
@ -328,7 +574,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
QrImageView(
|
||||
data: AddressUtils.buildUriString(
|
||||
coin,
|
||||
receivingAddress,
|
||||
_qrcodeContent ?? "",
|
||||
{},
|
||||
),
|
||||
size: MediaQuery.of(context).size.width / 2,
|
||||
|
@ -347,7 +593,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
RouteGenerator.useMaterialPageRoute,
|
||||
builder: (_) => GenerateUriQrCodeView(
|
||||
coin: coin,
|
||||
receivingAddress: receivingAddress,
|
||||
receivingAddress: _qrcodeContent ?? "",
|
||||
),
|
||||
settings: const RouteSettings(
|
||||
name: GenerateUriQrCodeView.routeName,
|
||||
|
|
|
@ -120,7 +120,7 @@ class _ConfirmTransactionViewState
|
|||
),
|
||||
);
|
||||
|
||||
late String txid;
|
||||
final List<String> txids = [];
|
||||
Future<TxData> txDataFuture;
|
||||
|
||||
final note = noteController.text;
|
||||
|
@ -140,10 +140,25 @@ class _ConfirmTransactionViewState
|
|||
} else if (widget.isPaynymTransaction) {
|
||||
txDataFuture = wallet.confirmSend(txData: widget.txData);
|
||||
} else {
|
||||
if (wallet is FiroWallet &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state ==
|
||||
"Private") {
|
||||
if (wallet is FiroWallet) {
|
||||
switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
|
||||
case FiroType.public:
|
||||
if (widget.txData.sparkMints == null) {
|
||||
txDataFuture = wallet.confirmSend(txData: widget.txData);
|
||||
} else {
|
||||
txDataFuture =
|
||||
wallet.confirmSparkMintTransactions(txData: widget.txData);
|
||||
}
|
||||
break;
|
||||
|
||||
case FiroType.lelantus:
|
||||
txDataFuture = wallet.confirmSendLelantus(txData: widget.txData);
|
||||
break;
|
||||
|
||||
case FiroType.spark:
|
||||
txDataFuture = wallet.confirmSendSpark(txData: widget.txData);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (coin == Coin.epicCash) {
|
||||
txDataFuture = wallet.confirmSend(
|
||||
|
@ -165,10 +180,16 @@ class _ConfirmTransactionViewState
|
|||
sendProgressController.triggerSuccess?.call();
|
||||
await Future<void>.delayed(const Duration(seconds: 5));
|
||||
|
||||
txid = (results.first as TxData).txid!;
|
||||
if (wallet is FiroWallet &&
|
||||
(results.first as TxData).sparkMints != null) {
|
||||
txids.addAll((results.first as TxData).sparkMints!.map((e) => e.txid!));
|
||||
} else {
|
||||
txids.add((results.first as TxData).txid!);
|
||||
}
|
||||
ref.refresh(desktopUseUTXOs);
|
||||
|
||||
// save note
|
||||
for (final txid in txids) {
|
||||
await ref.read(mainDBProvider).putTransactionNote(
|
||||
TransactionNote(
|
||||
walletId: walletId,
|
||||
|
@ -176,6 +197,7 @@ class _ConfirmTransactionViewState
|
|||
value: note,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.isTokenTx) {
|
||||
unawaited(ref.read(tokenServiceProvider)!.refresh());
|
||||
|
@ -233,10 +255,14 @@ class _ConfirmTransactionViewState
|
|||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
Text(
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: SelectableText(
|
||||
e.toString(),
|
||||
style: STextStyles.smallMed14(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 56,
|
||||
),
|
||||
|
@ -319,6 +345,48 @@ class _ConfirmTransactionViewState
|
|||
} else {
|
||||
unit = coin.ticker;
|
||||
}
|
||||
|
||||
final Amount? fee;
|
||||
final Amount amount;
|
||||
|
||||
final wallet = ref.watch(pWallets).getWallet(walletId);
|
||||
|
||||
if (wallet is FiroWallet) {
|
||||
switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
|
||||
case FiroType.public:
|
||||
if (widget.txData.sparkMints != null) {
|
||||
fee = widget.txData.sparkMints!
|
||||
.map((e) => e.fee!)
|
||||
.reduce((value, element) => value += element);
|
||||
amount = widget.txData.sparkMints!
|
||||
.map((e) => e.amountSpark!)
|
||||
.reduce((value, element) => value += element);
|
||||
} else {
|
||||
fee = widget.txData.fee;
|
||||
amount = widget.txData.amount!;
|
||||
}
|
||||
break;
|
||||
|
||||
case FiroType.lelantus:
|
||||
fee = widget.txData.fee;
|
||||
amount = widget.txData.amount!;
|
||||
break;
|
||||
|
||||
case FiroType.spark:
|
||||
fee = widget.txData.fee;
|
||||
amount = (widget.txData.amount ??
|
||||
Amount.zeroWith(
|
||||
fractionDigits: wallet.cryptoCurrency.fractionDigits)) +
|
||||
(widget.txData.amountSpark ??
|
||||
Amount.zeroWith(
|
||||
fractionDigits: wallet.cryptoCurrency.fractionDigits));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
fee = widget.txData.fee;
|
||||
amount = widget.txData.amount!;
|
||||
}
|
||||
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Background(
|
||||
|
@ -424,7 +492,8 @@ class _ConfirmTransactionViewState
|
|||
Text(
|
||||
widget.isPaynymTransaction
|
||||
? widget.txData.paynymAccountLite!.nymName
|
||||
: widget.txData.recipients!.first.address,
|
||||
: widget.txData.recipients?.first.address ??
|
||||
widget.txData.sparkRecipients!.first.address,
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
],
|
||||
|
@ -443,7 +512,7 @@ class _ConfirmTransactionViewState
|
|||
),
|
||||
SelectableText(
|
||||
ref.watch(pAmountFormatter(coin)).format(
|
||||
widget.txData.amount!,
|
||||
amount,
|
||||
ethContract: ref
|
||||
.watch(tokenServiceProvider)
|
||||
?.tokenContract,
|
||||
|
@ -468,9 +537,7 @@ class _ConfirmTransactionViewState
|
|||
style: STextStyles.smallMed12(context),
|
||||
),
|
||||
SelectableText(
|
||||
ref
|
||||
.watch(pAmountFormatter(coin))
|
||||
.format(widget.txData.fee!),
|
||||
ref.watch(pAmountFormatter(coin)).format(fee!),
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
|
@ -494,7 +561,7 @@ class _ConfirmTransactionViewState
|
|||
height: 4,
|
||||
),
|
||||
SelectableText(
|
||||
"~${widget.txData.fee!.raw.toInt() ~/ widget.txData.vSize!}",
|
||||
"~${fee!.raw.toInt() ~/ widget.txData.vSize!}",
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
],
|
||||
|
@ -625,7 +692,6 @@ class _ConfirmTransactionViewState
|
|||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final amount = widget.txData.amount!;
|
||||
final externalCalls = ref.watch(
|
||||
prefsChangeNotifierProvider.select(
|
||||
(value) => value.externalCalls));
|
||||
|
@ -723,9 +789,12 @@ class _ConfirmTransactionViewState
|
|||
height: 2,
|
||||
),
|
||||
SelectableText(
|
||||
// TODO: [prio=high] spark transaction specifics - better handling
|
||||
widget.isPaynymTransaction
|
||||
? widget.txData.paynymAccountLite!.nymName
|
||||
: widget.txData.recipients!.first.address,
|
||||
: widget.txData.recipients?.first.address ??
|
||||
widget.txData.sparkRecipients!.first
|
||||
.address,
|
||||
style: STextStyles.desktopTextExtraExtraSmall(
|
||||
context)
|
||||
.copyWith(
|
||||
|
@ -759,24 +828,15 @@ class _ConfirmTransactionViewState
|
|||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final fee = widget.txData.fee!;
|
||||
|
||||
return SelectableText(
|
||||
ref
|
||||
.watch(pAmountFormatter(coin))
|
||||
.format(fee),
|
||||
style:
|
||||
STextStyles.desktopTextExtraExtraSmall(
|
||||
SelectableText(
|
||||
ref.watch(pAmountFormatter(coin)).format(fee!),
|
||||
style: STextStyles.desktopTextExtraExtraSmall(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -981,15 +1041,9 @@ class _ConfirmTransactionViewState
|
|||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final fee = widget.txData.fee!;
|
||||
|
||||
return SelectableText(
|
||||
ref.watch(pAmountFormatter(coin)).format(fee),
|
||||
child: SelectableText(
|
||||
ref.watch(pAmountFormatter(coin)).format(fee!),
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -1025,7 +1079,7 @@ class _ConfirmTransactionViewState
|
|||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
child: SelectableText(
|
||||
"~${widget.txData.fee!.raw.toInt() ~/ widget.txData.vSize!}",
|
||||
"~${fee!.raw.toInt() ~/ widget.txData.vSize!}",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
),
|
||||
|
@ -1069,14 +1123,8 @@ class _ConfirmTransactionViewState
|
|||
.textConfirmTotalAmount,
|
||||
),
|
||||
),
|
||||
Builder(builder: (context) {
|
||||
final fee = widget.txData.fee!;
|
||||
|
||||
final amount = widget.txData.amount!;
|
||||
return SelectableText(
|
||||
ref
|
||||
.watch(pAmountFormatter(coin))
|
||||
.format(amount + fee),
|
||||
SelectableText(
|
||||
ref.watch(pAmountFormatter(coin)).format(amount + fee!),
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraExtraSmall(context)
|
||||
.copyWith(
|
||||
|
@ -1090,8 +1138,7 @@ class _ConfirmTransactionViewState
|
|||
.textConfirmTotalAmount,
|
||||
),
|
||||
textAlign: TextAlign.right,
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -101,9 +101,9 @@ class _FiroBalanceSelectionSheetState
|
|||
onTap: () {
|
||||
final state =
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state;
|
||||
if (state != "Private") {
|
||||
if (state != FiroType.spark) {
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
"Private";
|
||||
FiroType.spark;
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
|
@ -122,7 +122,7 @@ class _FiroBalanceSelectionSheetState
|
|||
activeColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.radioButtonIconEnabled,
|
||||
value: "Private",
|
||||
value: FiroType.spark,
|
||||
groupValue: ref
|
||||
.watch(
|
||||
publicPrivateBalanceStateProvider.state)
|
||||
|
@ -131,7 +131,7 @@ class _FiroBalanceSelectionSheetState
|
|||
ref
|
||||
.read(publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state = "Private";
|
||||
.state = FiroType.spark;
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
|
@ -149,7 +149,86 @@ class _FiroBalanceSelectionSheetState
|
|||
// Row(
|
||||
// children: [
|
||||
Text(
|
||||
"Private balance",
|
||||
"Spark balance",
|
||||
style: STextStyles.titleBold12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 2,
|
||||
),
|
||||
Text(
|
||||
ref.watch(pAmountFormatter(coin)).format(
|
||||
firoWallet
|
||||
.info.cachedBalanceTertiary.spendable,
|
||||
),
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
],
|
||||
),
|
||||
// ],
|
||||
// ),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
final state =
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state;
|
||||
if (state != FiroType.lelantus) {
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
FiroType.lelantus;
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Radio(
|
||||
activeColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.radioButtonIconEnabled,
|
||||
value: FiroType.lelantus,
|
||||
groupValue: ref
|
||||
.watch(
|
||||
publicPrivateBalanceStateProvider.state)
|
||||
.state,
|
||||
onChanged: (x) {
|
||||
ref
|
||||
.read(publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state = FiroType.lelantus;
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Row(
|
||||
// children: [
|
||||
Text(
|
||||
"Lelantus balance",
|
||||
style: STextStyles.titleBold12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
|
@ -180,9 +259,9 @@ class _FiroBalanceSelectionSheetState
|
|||
onTap: () {
|
||||
final state =
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state;
|
||||
if (state != "Public") {
|
||||
if (state != FiroType.public) {
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
"Public";
|
||||
FiroType.public;
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
|
@ -200,7 +279,7 @@ class _FiroBalanceSelectionSheetState
|
|||
activeColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.radioButtonIconEnabled,
|
||||
value: "Public",
|
||||
value: FiroType.public,
|
||||
groupValue: ref
|
||||
.watch(
|
||||
publicPrivateBalanceStateProvider.state)
|
||||
|
@ -209,7 +288,7 @@ class _FiroBalanceSelectionSheetState
|
|||
ref
|
||||
.read(publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state = "Public";
|
||||
.state = FiroType.public;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
|
|
|
@ -348,7 +348,7 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
|
|||
.getWallet(walletId)
|
||||
.cryptoCurrency
|
||||
.validateAddress(address ?? "");
|
||||
ref.read(previewTxButtonStateProvider.state).state =
|
||||
ref.read(previewTokenTxButtonStateProvider.state).state =
|
||||
(isValidAddress && amount != null && amount > Amount.zero);
|
||||
}
|
||||
|
||||
|
@ -1227,12 +1227,14 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
|
|||
),
|
||||
TextButton(
|
||||
onPressed: ref
|
||||
.watch(previewTxButtonStateProvider.state)
|
||||
.watch(
|
||||
previewTokenTxButtonStateProvider.state)
|
||||
.state
|
||||
? _previewTransaction
|
||||
: null,
|
||||
style: ref
|
||||
.watch(previewTxButtonStateProvider.state)
|
||||
.watch(
|
||||
previewTokenTxButtonStateProvider.state)
|
||||
.state
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
|
|
|
@ -15,14 +15,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:stackwallet/db/hive/db.dart';
|
||||
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/providers/global/debug_service_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
|
@ -217,98 +215,7 @@ class HiddenSettings extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}),
|
||||
// const SizedBox(
|
||||
// height: 12,
|
||||
// ),
|
||||
// Consumer(builder: (_, ref, __) {
|
||||
// return GestureDetector(
|
||||
// onTap: () async {
|
||||
// final x =
|
||||
// await MajesticBankAPI.instance.getRates();
|
||||
// print(x);
|
||||
// },
|
||||
// child: RoundedWhiteContainer(
|
||||
// child: Text(
|
||||
// "Click me",
|
||||
// style: STextStyles.button(context).copyWith(
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .accentColorDark),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }),
|
||||
// const SizedBox(
|
||||
// height: 12,
|
||||
// ),
|
||||
// Consumer(builder: (_, ref, __) {
|
||||
// return GestureDetector(
|
||||
// onTap: () async {
|
||||
// ref
|
||||
// .read(priceAnd24hChangeNotifierProvider)
|
||||
// .tokenContractAddressesToCheck
|
||||
// .add(
|
||||
// "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
|
||||
// ref
|
||||
// .read(priceAnd24hChangeNotifierProvider)
|
||||
// .tokenContractAddressesToCheck
|
||||
// .add(
|
||||
// "0xdAC17F958D2ee523a2206206994597C13D831ec7");
|
||||
// await ref
|
||||
// .read(priceAnd24hChangeNotifierProvider)
|
||||
// .updatePrice();
|
||||
//
|
||||
// final x = ref
|
||||
// .read(priceAnd24hChangeNotifierProvider)
|
||||
// .getTokenPrice(
|
||||
// "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
|
||||
//
|
||||
// print(
|
||||
// "PRICE 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48: $x");
|
||||
// },
|
||||
// child: RoundedWhiteContainer(
|
||||
// child: Text(
|
||||
// "Click me",
|
||||
// style: STextStyles.button(context).copyWith(
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .accentColorDark),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }),
|
||||
// const SizedBox(
|
||||
// height: 12,
|
||||
// ),
|
||||
// Consumer(builder: (_, ref, __) {
|
||||
// return GestureDetector(
|
||||
// onTap: () async {
|
||||
// // final erc20 = Erc20ContractInfo(
|
||||
// // contractAddress: 'some con',
|
||||
// // name: "loonamsn",
|
||||
// // symbol: "DD",
|
||||
// // decimals: 19,
|
||||
// // );
|
||||
// //
|
||||
// // final json = erc20.toJson();
|
||||
// //
|
||||
// // print(json);
|
||||
// //
|
||||
// // final ee = EthContractInfo.fromJson(json);
|
||||
// //
|
||||
// // print(ee);
|
||||
// },
|
||||
// child: RoundedWhiteContainer(
|
||||
// child: Text(
|
||||
// "Click me",
|
||||
// style: STextStyles.button(context).copyWith(
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .accentColorDark),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }),
|
||||
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
|
@ -345,9 +252,6 @@ class HiddenSettings extends StatelessWidget {
|
|||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return GestureDetector(
|
||||
|
@ -366,221 +270,6 @@ class HiddenSettings extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
try {
|
||||
final n = DefaultNodes.firoTestnet;
|
||||
|
||||
final e = ElectrumXClient.from(
|
||||
node: ElectrumXNode(
|
||||
address: n.host,
|
||||
port: n.port,
|
||||
name: n.name,
|
||||
id: n.id,
|
||||
useSSL: n.useSSL,
|
||||
),
|
||||
prefs:
|
||||
ref.read(prefsChangeNotifierProvider),
|
||||
failovers: [],
|
||||
);
|
||||
|
||||
// Call and print getSparkAnonymitySet.
|
||||
final anonymitySet =
|
||||
await e.getSparkAnonymitySet(
|
||||
coinGroupId: "1",
|
||||
startBlockHash: "",
|
||||
);
|
||||
|
||||
Util.printJson(anonymitySet, "anonymitySet");
|
||||
} catch (e, s) {
|
||||
print("$e\n$s");
|
||||
}
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"Spark getSparkAnonymitySet",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
try {
|
||||
final n = DefaultNodes.firoTestnet;
|
||||
|
||||
final e = ElectrumXClient.from(
|
||||
node: ElectrumXNode(
|
||||
address: n.host,
|
||||
port: n.port,
|
||||
name: n.name,
|
||||
id: n.id,
|
||||
useSSL: n.useSSL,
|
||||
),
|
||||
prefs:
|
||||
ref.read(prefsChangeNotifierProvider),
|
||||
failovers: [],
|
||||
);
|
||||
|
||||
// Call and print getUsedCoinsTags.
|
||||
final usedCoinsTags = await e
|
||||
.getSparkUsedCoinsTags(startNumber: 0);
|
||||
|
||||
print(
|
||||
"usedCoinsTags['tags'].length: ${usedCoinsTags["tags"].length}");
|
||||
Util.printJson(
|
||||
usedCoinsTags, "usedCoinsTags");
|
||||
} catch (e, s) {
|
||||
print("$e\n$s");
|
||||
}
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"Spark getSparkUsedCoinsTags",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
try {
|
||||
final n = DefaultNodes.firoTestnet;
|
||||
|
||||
final e = ElectrumXClient.from(
|
||||
node: ElectrumXNode(
|
||||
address: n.host,
|
||||
port: n.port,
|
||||
name: n.name,
|
||||
id: n.id,
|
||||
useSSL: n.useSSL,
|
||||
),
|
||||
prefs:
|
||||
ref.read(prefsChangeNotifierProvider),
|
||||
failovers: [],
|
||||
);
|
||||
|
||||
// Call and print getSparkMintMetaData.
|
||||
final mintMetaData =
|
||||
await e.getSparkMintMetaData(
|
||||
sparkCoinHashes: [
|
||||
"b476ed2b374bb081ea51d111f68f0136252521214e213d119b8dc67b92f5a390",
|
||||
],
|
||||
);
|
||||
|
||||
Util.printJson(mintMetaData, "mintMetaData");
|
||||
} catch (e, s) {
|
||||
print("$e\n$s");
|
||||
}
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"Spark getSparkMintMetaData",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
try {
|
||||
final n = DefaultNodes.firoTestnet;
|
||||
|
||||
final e = ElectrumXClient.from(
|
||||
node: ElectrumXNode(
|
||||
address: n.host,
|
||||
port: n.port,
|
||||
name: n.name,
|
||||
id: n.id,
|
||||
useSSL: n.useSSL,
|
||||
),
|
||||
prefs:
|
||||
ref.read(prefsChangeNotifierProvider),
|
||||
failovers: [],
|
||||
);
|
||||
|
||||
// Call and print getSparkLatestCoinId.
|
||||
final latestCoinId =
|
||||
await e.getSparkLatestCoinId();
|
||||
|
||||
Util.printJson(latestCoinId, "latestCoinId");
|
||||
} catch (e, s) {
|
||||
print("$e\n$s");
|
||||
}
|
||||
},
|
||||
child: RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"Spark getSparkLatestCoinId",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// const SizedBox(
|
||||
// height: 12,
|
||||
// ),
|
||||
// GestureDetector(
|
||||
// onTap: () async {
|
||||
// showDialog<void>(
|
||||
// context: context,
|
||||
// builder: (_) {
|
||||
// return StackDialogBase(
|
||||
// child: SizedBox(
|
||||
// width: 300,
|
||||
// child: Lottie.asset(
|
||||
// Assets.lottie.plain(Coin.bitcoincash),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// },
|
||||
// child: RoundedWhiteContainer(
|
||||
// child: Text(
|
||||
// "Lottie test",
|
||||
// style: STextStyles.button(context).copyWith(
|
||||
// color: Theme.of(context)
|
||||
// .extension<StackColors>()!
|
||||
// .accentColorDark),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -40,13 +40,16 @@ class TxIcon extends ConsumerWidget {
|
|||
bool isReceived,
|
||||
bool isPending,
|
||||
TransactionSubType subType,
|
||||
TransactionType type,
|
||||
IThemeAssets assets,
|
||||
) {
|
||||
if (subType == TransactionSubType.cashFusion) {
|
||||
return Assets.svg.txCashFusion;
|
||||
}
|
||||
|
||||
if (!isReceived && subType == TransactionSubType.mint) {
|
||||
if ((!isReceived && subType == TransactionSubType.mint) ||
|
||||
(subType == TransactionSubType.sparkMint &&
|
||||
type == TransactionType.sentToSelf)) {
|
||||
if (isCancelled) {
|
||||
return Assets.svg.anonymizeFailed;
|
||||
}
|
||||
|
@ -91,6 +94,7 @@ class TxIcon extends ConsumerWidget {
|
|||
ref.watch(pWallets).getWallet(tx.walletId).cryptoCurrency.minConfirms,
|
||||
),
|
||||
tx.subType,
|
||||
tx.type,
|
||||
ref.watch(themeAssetsProvider),
|
||||
);
|
||||
} else if (transaction is TransactionV2) {
|
||||
|
@ -104,6 +108,7 @@ class TxIcon extends ConsumerWidget {
|
|||
ref.watch(pWallets).getWallet(tx.walletId).cryptoCurrency.minConfirms,
|
||||
),
|
||||
tx.subType,
|
||||
tx.type,
|
||||
ref.watch(themeAssetsProvider),
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -25,8 +25,10 @@ import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
|
|||
enum _BalanceType {
|
||||
available,
|
||||
full,
|
||||
privateAvailable,
|
||||
privateFull;
|
||||
lelantusAvailable,
|
||||
lelantusFull,
|
||||
sparkAvailable,
|
||||
sparkFull;
|
||||
}
|
||||
|
||||
class WalletBalanceToggleSheet extends ConsumerWidget {
|
||||
|
@ -39,9 +41,10 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final maxHeight = MediaQuery.of(context).size.height * 0.60;
|
||||
final maxHeight = MediaQuery.of(context).size.height * 0.90;
|
||||
|
||||
final coin = ref.watch(pWalletCoin(walletId));
|
||||
final isFiro = coin == Coin.firo || coin == Coin.firoTestNet;
|
||||
|
||||
Balance balance = ref.watch(pWalletBalance(walletId));
|
||||
|
||||
|
@ -52,18 +55,27 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
|
|||
: _BalanceType.full;
|
||||
|
||||
Balance? balanceSecondary;
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
Balance? balanceTertiary;
|
||||
if (isFiro) {
|
||||
balanceSecondary = ref.watch(pWalletBalanceSecondary(walletId));
|
||||
balanceTertiary = ref.watch(pWalletBalanceTertiary(walletId));
|
||||
|
||||
final temp = balance;
|
||||
balance = balanceSecondary!;
|
||||
balanceSecondary = temp;
|
||||
|
||||
if (ref.watch(publicPrivateBalanceStateProvider.state).state ==
|
||||
"Private") {
|
||||
switch (ref.watch(publicPrivateBalanceStateProvider.state).state) {
|
||||
case FiroType.spark:
|
||||
_bal = _bal == _BalanceType.available
|
||||
? _BalanceType.privateAvailable
|
||||
: _BalanceType.privateFull;
|
||||
? _BalanceType.sparkAvailable
|
||||
: _BalanceType.sparkFull;
|
||||
break;
|
||||
|
||||
case FiroType.lelantus:
|
||||
_bal = _bal == _BalanceType.available
|
||||
? _BalanceType.lelantusAvailable
|
||||
: _BalanceType.lelantusFull;
|
||||
break;
|
||||
|
||||
case FiroType.public:
|
||||
// already set above
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,22 +128,21 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
|
|||
height: 24,
|
||||
),
|
||||
BalanceSelector(
|
||||
title:
|
||||
"Available${balanceSecondary != null ? " public" : ""} balance",
|
||||
title: "Available${isFiro ? " public" : ""} balance",
|
||||
coin: coin,
|
||||
balance: balance.spendable,
|
||||
onPressed: () {
|
||||
ref.read(walletBalanceToggleStateProvider.state).state =
|
||||
WalletBalanceToggleState.available;
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
"Public";
|
||||
FiroType.public;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
onChanged: (_) {
|
||||
ref.read(walletBalanceToggleStateProvider.state).state =
|
||||
WalletBalanceToggleState.available;
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
"Public";
|
||||
FiroType.public;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
value: _BalanceType.available,
|
||||
|
@ -141,22 +152,21 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
|
|||
height: 12,
|
||||
),
|
||||
BalanceSelector(
|
||||
title:
|
||||
"Full${balanceSecondary != null ? " public" : ""} balance",
|
||||
title: "Full${isFiro ? " public" : ""} balance",
|
||||
coin: coin,
|
||||
balance: balance.total,
|
||||
onPressed: () {
|
||||
ref.read(walletBalanceToggleStateProvider.state).state =
|
||||
WalletBalanceToggleState.full;
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
"Public";
|
||||
FiroType.public;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
onChanged: (_) {
|
||||
ref.read(walletBalanceToggleStateProvider.state).state =
|
||||
WalletBalanceToggleState.full;
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
"Public";
|
||||
FiroType.public;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
value: _BalanceType.full,
|
||||
|
@ -168,24 +178,24 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
|
|||
),
|
||||
if (balanceSecondary != null)
|
||||
BalanceSelector(
|
||||
title: "Available private balance",
|
||||
title: "Available lelantus balance",
|
||||
coin: coin,
|
||||
balance: balanceSecondary.spendable,
|
||||
onPressed: () {
|
||||
ref.read(walletBalanceToggleStateProvider.state).state =
|
||||
WalletBalanceToggleState.available;
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
"Private";
|
||||
FiroType.lelantus;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
onChanged: (_) {
|
||||
ref.read(walletBalanceToggleStateProvider.state).state =
|
||||
WalletBalanceToggleState.available;
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
"Private";
|
||||
FiroType.lelantus;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
value: _BalanceType.privateAvailable,
|
||||
value: _BalanceType.lelantusAvailable,
|
||||
groupValue: _bal,
|
||||
),
|
||||
if (balanceSecondary != null)
|
||||
|
@ -194,24 +204,76 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
|
|||
),
|
||||
if (balanceSecondary != null)
|
||||
BalanceSelector(
|
||||
title: "Full private balance",
|
||||
title: "Full lelantus balance",
|
||||
coin: coin,
|
||||
balance: balanceSecondary.total,
|
||||
onPressed: () {
|
||||
ref.read(walletBalanceToggleStateProvider.state).state =
|
||||
WalletBalanceToggleState.full;
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
"Private";
|
||||
FiroType.lelantus;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
onChanged: (_) {
|
||||
ref.read(walletBalanceToggleStateProvider.state).state =
|
||||
WalletBalanceToggleState.full;
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
"Private";
|
||||
FiroType.lelantus;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
value: _BalanceType.privateFull,
|
||||
value: _BalanceType.lelantusFull,
|
||||
groupValue: _bal,
|
||||
),
|
||||
if (balanceTertiary != null)
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
if (balanceTertiary != null)
|
||||
BalanceSelector(
|
||||
title: "Available spark balance",
|
||||
coin: coin,
|
||||
balance: balanceTertiary.spendable,
|
||||
onPressed: () {
|
||||
ref.read(walletBalanceToggleStateProvider.state).state =
|
||||
WalletBalanceToggleState.available;
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
FiroType.spark;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
onChanged: (_) {
|
||||
ref.read(walletBalanceToggleStateProvider.state).state =
|
||||
WalletBalanceToggleState.available;
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
FiroType.spark;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
value: _BalanceType.sparkAvailable,
|
||||
groupValue: _bal,
|
||||
),
|
||||
if (balanceTertiary != null)
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
if (balanceTertiary != null)
|
||||
BalanceSelector(
|
||||
title: "Full spark balance",
|
||||
coin: coin,
|
||||
balance: balanceTertiary.total,
|
||||
onPressed: () {
|
||||
ref.read(walletBalanceToggleStateProvider.state).state =
|
||||
WalletBalanceToggleState.full;
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
FiroType.spark;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
onChanged: (_) {
|
||||
ref.read(walletBalanceToggleStateProvider.state).state =
|
||||
WalletBalanceToggleState.full;
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
FiroType.spark;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
value: _BalanceType.sparkFull,
|
||||
groupValue: _bal,
|
||||
),
|
||||
const SizedBox(
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'dart:io';
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_native_splash/cli_commands.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart';
|
||||
|
@ -29,6 +30,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
|||
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
|
||||
import 'package:stackwallet/wallets/wallet/impl/banano_wallet.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
|
||||
class WalletSummaryInfo extends ConsumerWidget {
|
||||
|
@ -45,6 +47,8 @@ class WalletSummaryInfo extends ConsumerWidget {
|
|||
showModalBottomSheet<dynamic>(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
useSafeArea: true,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
|
@ -58,10 +62,6 @@ class WalletSummaryInfo extends ConsumerWidget {
|
|||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
bool isMonkey = true;
|
||||
|
||||
final receivingAddress = ref.watch(pWalletReceivingAddress(walletId));
|
||||
|
||||
final externalCalls = ref.watch(
|
||||
prefsChangeNotifierProvider.select((value) => value.externalCalls));
|
||||
final coin = ref.watch(pWalletCoin(walletId));
|
||||
|
@ -81,19 +81,28 @@ class WalletSummaryInfo extends ConsumerWidget {
|
|||
WalletBalanceToggleState.available;
|
||||
|
||||
final Amount balanceToShow;
|
||||
String title;
|
||||
final String title;
|
||||
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
final _showPrivate =
|
||||
ref.watch(publicPrivateBalanceStateProvider.state).state == "Private";
|
||||
final type = ref.watch(publicPrivateBalanceStateProvider.state).state;
|
||||
title =
|
||||
"${_showAvailable ? "Available" : "Full"} ${type.name.capitalize()} balance";
|
||||
switch (type) {
|
||||
case FiroType.spark:
|
||||
final balance = ref.watch(pWalletBalanceTertiary(walletId));
|
||||
balanceToShow = _showAvailable ? balance.spendable : balance.total;
|
||||
break;
|
||||
|
||||
final secondaryBal = ref.watch(pWalletBalanceSecondary(walletId));
|
||||
case FiroType.lelantus:
|
||||
final balance = ref.watch(pWalletBalanceSecondary(walletId));
|
||||
balanceToShow = _showAvailable ? balance.spendable : balance.total;
|
||||
break;
|
||||
|
||||
final bal = _showPrivate ? balance : secondaryBal;
|
||||
|
||||
balanceToShow = _showAvailable ? bal.spendable : bal.total;
|
||||
title = _showAvailable ? "Available" : "Full";
|
||||
title += _showPrivate ? " private balance" : " public balance";
|
||||
case FiroType.public:
|
||||
final balance = ref.watch(pWalletBalance(walletId));
|
||||
balanceToShow = _showAvailable ? balance.spendable : balance.total;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
balanceToShow = _showAvailable ? balance.spendable : balance.total;
|
||||
title = _showAvailable ? "Available balance" : "Full balance";
|
||||
|
@ -102,8 +111,8 @@ class WalletSummaryInfo extends ConsumerWidget {
|
|||
List<int>? imageBytes;
|
||||
|
||||
if (coin == Coin.banano) {
|
||||
// TODO: [prio=high] fix this and uncomment:
|
||||
// imageBytes = (manager.wallet as BananoWallet).getMonkeyImageBytes();
|
||||
imageBytes = (ref.watch(pWallets).getWallet(walletId) as BananoWallet)
|
||||
.getMonkeyImageBytes();
|
||||
}
|
||||
|
||||
return ConditionalParent(
|
||||
|
|
|
@ -31,7 +31,6 @@ import 'package:stackwallet/utilities/amount/amount.dart';
|
|||
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
|
@ -843,34 +842,10 @@ class _DesktopTransactionCardRowState
|
|||
late final String walletId;
|
||||
late final int minConfirms;
|
||||
|
||||
String whatIsIt(TransactionType type, Coin coin, int height) {
|
||||
if (_transaction.subType == TransactionSubType.mint ||
|
||||
_transaction.subType == TransactionSubType.cashFusion) {
|
||||
if (_transaction.isConfirmed(height, minConfirms)) {
|
||||
return "Anonymized";
|
||||
} else {
|
||||
return "Anonymizing";
|
||||
}
|
||||
}
|
||||
|
||||
if (type == TransactionType.incoming) {
|
||||
if (_transaction.isConfirmed(height, minConfirms)) {
|
||||
return "Received";
|
||||
} else {
|
||||
return "Receiving";
|
||||
}
|
||||
} else if (type == TransactionType.outgoing) {
|
||||
if (_transaction.isConfirmed(height, minConfirms)) {
|
||||
return "Sent";
|
||||
} else {
|
||||
return "Sending";
|
||||
}
|
||||
} else if (type == TransactionType.sentToSelf) {
|
||||
return "Sent to self";
|
||||
} else {
|
||||
return type.name;
|
||||
}
|
||||
}
|
||||
String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel(
|
||||
currentChainHeight: height,
|
||||
minConfirms: minConfirms,
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -917,7 +892,7 @@ class _DesktopTransactionCardRowState
|
|||
final Amount amount;
|
||||
|
||||
if (_transaction.subType == TransactionSubType.cashFusion) {
|
||||
amount = _transaction.getAmountReceivedThisWallet(coin: coin);
|
||||
amount = _transaction.getAmountReceivedInThisWallet(coin: coin);
|
||||
} else {
|
||||
switch (_transaction.type) {
|
||||
case TransactionType.outgoing:
|
||||
|
@ -926,7 +901,11 @@ class _DesktopTransactionCardRowState
|
|||
|
||||
case TransactionType.incoming:
|
||||
case TransactionType.sentToSelf:
|
||||
amount = _transaction.getAmountReceivedThisWallet(coin: coin);
|
||||
if (_transaction.subType == TransactionSubType.sparkMint) {
|
||||
amount = _transaction.getAmountSparkSelfMinted(coin: coin);
|
||||
} else {
|
||||
amount = _transaction.getAmountReceivedInThisWallet(coin: coin);
|
||||
}
|
||||
break;
|
||||
|
||||
case TransactionType.unknown:
|
||||
|
@ -994,8 +973,7 @@ class _DesktopTransactionCardRowState
|
|||
flex: 3,
|
||||
child: Text(
|
||||
whatIsIt(
|
||||
_transaction.type,
|
||||
coin,
|
||||
_transaction,
|
||||
currentHeight,
|
||||
),
|
||||
style:
|
||||
|
|
|
@ -44,42 +44,13 @@ class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
|
|||
String whatIsIt(
|
||||
Coin coin,
|
||||
int currentHeight,
|
||||
) {
|
||||
final confirmedStatus = _transaction.isConfirmed(
|
||||
currentHeight,
|
||||
) =>
|
||||
_transaction.statusLabel(
|
||||
currentChainHeight: currentHeight,
|
||||
minConfirms:
|
||||
ref.read(pWallets).getWallet(walletId).cryptoCurrency.minConfirms,
|
||||
);
|
||||
|
||||
if (_transaction.subType == TransactionSubType.cashFusion) {
|
||||
if (confirmedStatus) {
|
||||
return "Anonymized";
|
||||
} else {
|
||||
return "Anonymizing";
|
||||
}
|
||||
}
|
||||
|
||||
if (_transaction.type == TransactionType.incoming) {
|
||||
// if (_transaction.isMinting) {
|
||||
// return "Minting";
|
||||
// } else
|
||||
if (confirmedStatus) {
|
||||
return "Received";
|
||||
} else {
|
||||
return "Receiving";
|
||||
}
|
||||
} else if (_transaction.type == TransactionType.outgoing) {
|
||||
if (confirmedStatus) {
|
||||
return "Sent";
|
||||
} else {
|
||||
return "Sending";
|
||||
}
|
||||
} else if (_transaction.type == TransactionType.sentToSelf) {
|
||||
return "Sent to self";
|
||||
} else {
|
||||
return _transaction.type.name;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_transaction = widget.transaction;
|
||||
|
@ -121,7 +92,7 @@ class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
|
|||
final Amount amount;
|
||||
|
||||
if (_transaction.subType == TransactionSubType.cashFusion) {
|
||||
amount = _transaction.getAmountReceivedThisWallet(coin: coin);
|
||||
amount = _transaction.getAmountReceivedInThisWallet(coin: coin);
|
||||
} else {
|
||||
switch (_transaction.type) {
|
||||
case TransactionType.outgoing:
|
||||
|
@ -130,7 +101,11 @@ class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
|
|||
|
||||
case TransactionType.incoming:
|
||||
case TransactionType.sentToSelf:
|
||||
amount = _transaction.getAmountReceivedThisWallet(coin: coin);
|
||||
if (_transaction.subType == TransactionSubType.sparkMint) {
|
||||
amount = _transaction.getAmountSparkSelfMinted(coin: coin);
|
||||
} else {
|
||||
amount = _transaction.getAmountReceivedInThisWallet(coin: coin);
|
||||
}
|
||||
break;
|
||||
|
||||
case TransactionType.unknown:
|
||||
|
|
|
@ -95,7 +95,12 @@ class _TransactionV2DetailsViewState
|
|||
minConfirms =
|
||||
ref.read(pWallets).getWallet(walletId).cryptoCurrency.minConfirms;
|
||||
|
||||
if (_transaction.subType == TransactionSubType.join ||
|
||||
_transaction.subType == TransactionSubType.sparkSpend) {
|
||||
fee = _transaction.getAnonFee()!;
|
||||
} else {
|
||||
fee = _transaction.getFee(coin: coin);
|
||||
}
|
||||
|
||||
if (_transaction.subType == TransactionSubType.cashFusion ||
|
||||
_transaction.type == TransactionType.sentToSelf) {
|
||||
|
@ -107,7 +112,7 @@ class _TransactionV2DetailsViewState
|
|||
unit = coin.ticker;
|
||||
|
||||
if (_transaction.subType == TransactionSubType.cashFusion) {
|
||||
amount = _transaction.getAmountReceivedThisWallet(coin: coin);
|
||||
amount = _transaction.getAmountReceivedInThisWallet(coin: coin);
|
||||
data = _transaction.outputs
|
||||
.where((e) => e.walletOwns)
|
||||
.map((e) => (
|
||||
|
@ -131,7 +136,11 @@ class _TransactionV2DetailsViewState
|
|||
|
||||
case TransactionType.incoming:
|
||||
case TransactionType.sentToSelf:
|
||||
amount = _transaction.getAmountReceivedThisWallet(coin: coin);
|
||||
if (_transaction.subType == TransactionSubType.sparkMint) {
|
||||
amount = _transaction.getAmountSparkSelfMinted(coin: coin);
|
||||
} else {
|
||||
amount = _transaction.getAmountReceivedInThisWallet(coin: coin);
|
||||
}
|
||||
data = _transaction.outputs
|
||||
.where((e) => e.walletOwns)
|
||||
.map((e) => (
|
||||
|
@ -164,77 +173,10 @@ class _TransactionV2DetailsViewState
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
String whatIsIt(TransactionV2 tx, int height) {
|
||||
final type = tx.type;
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
if (tx.subType == TransactionSubType.mint) {
|
||||
if (tx.isConfirmed(height, minConfirms)) {
|
||||
return "Minted";
|
||||
} else {
|
||||
return "Minting";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if (coin == Coin.epicCash) {
|
||||
// if (_transaction.isCancelled) {
|
||||
// return "Cancelled";
|
||||
// } else if (type == TransactionType.incoming) {
|
||||
// if (tx.isConfirmed(height, minConfirms)) {
|
||||
// return "Received";
|
||||
// } else {
|
||||
// if (_transaction.numberOfMessages == 1) {
|
||||
// return "Receiving (waiting for sender)";
|
||||
// } else if ((_transaction.numberOfMessages ?? 0) > 1) {
|
||||
// return "Receiving (waiting for confirmations)"; // TODO test if the sender still has to open again after the receiver has 2 messages present, ie. sender->receiver->sender->node (yes) vs. sender->receiver->node (no)
|
||||
// } else {
|
||||
// return "Receiving";
|
||||
// }
|
||||
// }
|
||||
// } else if (type == TransactionType.outgoing) {
|
||||
// if (tx.isConfirmed(height, minConfirms)) {
|
||||
// return "Sent (confirmed)";
|
||||
// } else {
|
||||
// if (_transaction.numberOfMessages == 1) {
|
||||
// return "Sending (waiting for receiver)";
|
||||
// } else if ((_transaction.numberOfMessages ?? 0) > 1) {
|
||||
// return "Sending (waiting for confirmations)";
|
||||
// } else {
|
||||
// return "Sending";
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if (tx.subType == TransactionSubType.cashFusion) {
|
||||
if (tx.isConfirmed(height, minConfirms)) {
|
||||
return "Anonymized";
|
||||
} else {
|
||||
return "Anonymizing";
|
||||
}
|
||||
}
|
||||
|
||||
if (type == TransactionType.incoming) {
|
||||
// if (_transaction.isMinting) {
|
||||
// return "Minting";
|
||||
// } else
|
||||
if (tx.isConfirmed(height, minConfirms)) {
|
||||
return "Received";
|
||||
} else {
|
||||
return "Receiving";
|
||||
}
|
||||
} else if (type == TransactionType.outgoing) {
|
||||
if (tx.isConfirmed(height, minConfirms)) {
|
||||
return "Sent";
|
||||
} else {
|
||||
return "Sending";
|
||||
}
|
||||
} else if (type == TransactionType.sentToSelf) {
|
||||
return "Sent to self";
|
||||
} else {
|
||||
return type.name;
|
||||
}
|
||||
}
|
||||
String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel(
|
||||
currentChainHeight: height,
|
||||
minConfirms: minConfirms,
|
||||
);
|
||||
|
||||
Future<String> fetchContactNameFor(String address) async {
|
||||
if (address.isEmpty) {
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
@ -40,6 +42,9 @@ class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
|
|||
bool _hasLoaded = false;
|
||||
List<TransactionV2> _transactions = [];
|
||||
|
||||
late final StreamSubscription<List<TransactionV2>> _subscription;
|
||||
late final QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> _query;
|
||||
|
||||
BorderRadius get _borderRadiusFirst {
|
||||
return BorderRadius.only(
|
||||
topLeft: Radius.circular(
|
||||
|
@ -63,18 +68,38 @@ class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
|
|||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final coin = ref.watch(pWallets).getWallet(widget.walletId).info.coin;
|
||||
|
||||
return FutureBuilder(
|
||||
future: ref
|
||||
.watch(mainDBProvider)
|
||||
void initState() {
|
||||
_query = ref
|
||||
.read(mainDBProvider)
|
||||
.isar
|
||||
.transactionV2s
|
||||
.where()
|
||||
.walletIdEqualTo(widget.walletId)
|
||||
.sortByTimestampDesc()
|
||||
.findAll(),
|
||||
.sortByTimestampDesc();
|
||||
|
||||
_subscription = _query.watch().listen((event) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
_transactions = event;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final coin = ref.watch(pWallets).getWallet(widget.walletId).info.coin;
|
||||
|
||||
return FutureBuilder(
|
||||
future: _query.findAll(),
|
||||
builder: (fbContext, AsyncSnapshot<List<TransactionV2>> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
|
|
|
@ -46,8 +46,6 @@ import 'package:stackwallet/providers/providers.dart';
|
|||
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
||||
import 'package:stackwallet/providers/ui/unread_notifications_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||
|
@ -63,7 +61,6 @@ import 'package:stackwallet/utilities/clipboard_interface.dart';
|
|||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/show_loading.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
|
@ -71,6 +68,7 @@ import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
|
|||
import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
@ -118,6 +116,8 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
late final String walletId;
|
||||
late final Coin coin;
|
||||
|
||||
late final bool isSparkWallet;
|
||||
|
||||
late final bool _shouldDisableAutoSyncOnLogOut;
|
||||
|
||||
late WalletSyncStatus _currentSyncStatus;
|
||||
|
@ -174,6 +174,8 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
_shouldDisableAutoSyncOnLogOut = false;
|
||||
}
|
||||
|
||||
isSparkWallet = wallet is SparkInterface;
|
||||
|
||||
if (coin == Coin.firo &&
|
||||
(wallet as FiroWallet).lelantusCoinIsarRescanRequired) {
|
||||
_rescanningOnOpen = true;
|
||||
|
@ -433,7 +435,8 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
}
|
||||
|
||||
try {
|
||||
await firoWallet.anonymizeAllPublicFunds();
|
||||
// await firoWallet.anonymizeAllLelantus();
|
||||
await firoWallet.anonymizeAllSpark();
|
||||
shouldPop = true;
|
||||
if (mounted) {
|
||||
Navigator.of(context).popUntil(
|
||||
|
@ -760,11 +763,11 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (coin == Coin.firo)
|
||||
if (isSparkWallet)
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
if (coin == Coin.firo)
|
||||
if (isSparkWallet)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
|
@ -951,20 +954,21 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
label: "Send",
|
||||
icon: const SendNavIcon(),
|
||||
onTap: () {
|
||||
switch (ref
|
||||
.read(walletBalanceToggleStateProvider.state)
|
||||
.state) {
|
||||
case WalletBalanceToggleState.full:
|
||||
ref
|
||||
.read(publicPrivateBalanceStateProvider.state)
|
||||
.state = "Public";
|
||||
break;
|
||||
case WalletBalanceToggleState.available:
|
||||
ref
|
||||
.read(publicPrivateBalanceStateProvider.state)
|
||||
.state = "Private";
|
||||
break;
|
||||
}
|
||||
// not sure what this is supposed to accomplish?
|
||||
// switch (ref
|
||||
// .read(walletBalanceToggleStateProvider.state)
|
||||
// .state) {
|
||||
// case WalletBalanceToggleState.full:
|
||||
// ref
|
||||
// .read(publicPrivateBalanceStateProvider.state)
|
||||
// .state = "Public";
|
||||
// break;
|
||||
// case WalletBalanceToggleState.available:
|
||||
// ref
|
||||
// .read(publicPrivateBalanceStateProvider.state)
|
||||
// .state = "Private";
|
||||
// break;
|
||||
// }
|
||||
Navigator.of(context).pushNamed(
|
||||
SendView.routeName,
|
||||
arguments: Tuple2(
|
||||
|
|
|
@ -16,6 +16,7 @@ import 'package:stackwallet/pages/wallets_view/sub_widgets/empty_wallets.dart';
|
|||
import 'package:stackwallet/pages/wallets_view/sub_widgets/favorite_wallets.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/themes/theme_providers.dart';
|
||||
import 'package:stackwallet/wallets/isar/providers/all_wallets_info_provider.dart';
|
||||
|
||||
class WalletsView extends ConsumerWidget {
|
||||
const WalletsView({Key? key}) : super(key: key);
|
||||
|
@ -25,7 +26,7 @@ class WalletsView extends ConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
final hasWallets = ref.watch(pWallets).hasWallets;
|
||||
final hasWallets = ref.watch(pAllWalletsInfo).isNotEmpty;
|
||||
|
||||
final showFavorites = ref.watch(prefsChangeNotifierProvider
|
||||
.select((value) => value.showFavoriteWallets));
|
||||
|
|
|
@ -16,9 +16,9 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||
import 'package:stackwallet/pages/settings_views/global_settings_view/hidden_settings.dart';
|
||||
import 'package:stackwallet/pages/wallets_view/sub_widgets/empty_wallets.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/my_wallets.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/themes/theme_providers.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/wallets/isar/providers/all_wallets_info_provider.dart';
|
||||
import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart';
|
||||
import 'package:stackwallet/widgets/background.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||
|
@ -36,7 +36,7 @@ class _MyStackViewState extends ConsumerState<MyStackView> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
final hasWallets = ref.watch(pWallets).hasWallets;
|
||||
final hasWallets = ref.watch(pAllWalletsInfo).isNotEmpty;
|
||||
|
||||
return Background(
|
||||
child: Column(
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
|
@ -80,6 +81,8 @@ class DesktopPrivateBalanceToggleButton extends ConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final currentType = ref.watch(publicPrivateBalanceStateProvider);
|
||||
|
||||
return SizedBox(
|
||||
height: 22,
|
||||
width: 22,
|
||||
|
@ -87,13 +90,21 @@ class DesktopPrivateBalanceToggleButton extends ConsumerWidget {
|
|||
color: Theme.of(context).extension<StackColors>()!.buttonBackSecondary,
|
||||
splashColor: Theme.of(context).extension<StackColors>()!.highlight,
|
||||
onPressed: () {
|
||||
if (ref.read(walletPrivateBalanceToggleStateProvider.state).state ==
|
||||
WalletBalanceToggleState.available) {
|
||||
ref.read(walletPrivateBalanceToggleStateProvider.state).state =
|
||||
WalletBalanceToggleState.full;
|
||||
} else {
|
||||
ref.read(walletPrivateBalanceToggleStateProvider.state).state =
|
||||
WalletBalanceToggleState.available;
|
||||
switch (currentType) {
|
||||
case FiroType.public:
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
FiroType.lelantus;
|
||||
break;
|
||||
|
||||
case FiroType.lelantus:
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
FiroType.spark;
|
||||
break;
|
||||
|
||||
case FiroType.spark:
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
FiroType.public;
|
||||
break;
|
||||
}
|
||||
onPressed?.call();
|
||||
},
|
||||
|
@ -110,12 +121,14 @@ class DesktopPrivateBalanceToggleButton extends ConsumerWidget {
|
|||
child: Center(
|
||||
child: Image(
|
||||
image: AssetImage(
|
||||
ref.watch(walletPrivateBalanceToggleStateProvider.state).state ==
|
||||
WalletBalanceToggleState.available
|
||||
? Assets.png.glassesHidden
|
||||
: Assets.png.glasses,
|
||||
currentType == FiroType.public
|
||||
? Assets.png.glasses
|
||||
: Assets.png.glassesHidden,
|
||||
),
|
||||
width: 16,
|
||||
color: currentType == FiroType.spark
|
||||
? Theme.of(context).extension<StackColors>()!.accentColorYellow
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -10,14 +10,18 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
|
||||
import 'package:stackwallet/pages/token_view/token_view.dart';
|
||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
|
@ -29,7 +33,9 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
|||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
|
||||
import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||
|
@ -57,10 +63,15 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
|
|||
late final Coin coin;
|
||||
late final String walletId;
|
||||
late final ClipboardInterface clipboard;
|
||||
late final bool supportsSpark;
|
||||
|
||||
String? _sparkAddress;
|
||||
String? _qrcodeContent;
|
||||
bool _showSparkAddress = true;
|
||||
|
||||
Future<void> generateNewAddress() async {
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
if (wallet is Bip39HDWallet) {
|
||||
if (wallet is MultiAddressInterface) {
|
||||
bool shouldPop = false;
|
||||
unawaited(
|
||||
showDialog(
|
||||
|
@ -93,30 +104,273 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> generateNewSparkAddress() async {
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
if (wallet is SparkInterface) {
|
||||
bool shouldPop = false;
|
||||
unawaited(
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => shouldPop,
|
||||
child: Container(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.overlay
|
||||
.withOpacity(0.5),
|
||||
child: const CustomLoadingOverlay(
|
||||
message: "Generating address",
|
||||
eventBus: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
final address = await wallet.generateNextSparkAddress();
|
||||
await ref.read(mainDBProvider).isar.writeTxn(() async {
|
||||
await ref.read(mainDBProvider).isar.addresses.put(address);
|
||||
});
|
||||
|
||||
shouldPop = true;
|
||||
|
||||
if (mounted) {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
if (_sparkAddress != address.value) {
|
||||
setState(() {
|
||||
_sparkAddress = address.value;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StreamSubscription<Address?>? _streamSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
walletId = widget.walletId;
|
||||
coin = ref.read(pWalletInfo(walletId)).coin;
|
||||
clipboard = widget.clipboard;
|
||||
supportsSpark = ref.read(pWallets).getWallet(walletId) is SparkInterface;
|
||||
|
||||
if (supportsSpark) {
|
||||
_streamSub = ref
|
||||
.read(mainDBProvider)
|
||||
.isar
|
||||
.addresses
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.typeEqualTo(AddressType.spark)
|
||||
.sortByDerivationIndexDesc()
|
||||
.findFirst()
|
||||
.asStream()
|
||||
.listen((event) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_sparkAddress = event?.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_streamSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
final receivingAddress = ref.watch(pWalletReceivingAddress(walletId));
|
||||
if (supportsSpark) {
|
||||
if (_showSparkAddress) {
|
||||
_qrcodeContent = _sparkAddress;
|
||||
} else {
|
||||
_qrcodeContent = ref.watch(pWalletReceivingAddress(walletId));
|
||||
}
|
||||
} else {
|
||||
_qrcodeContent = ref.watch(pWalletReceivingAddress(walletId));
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
ConditionalParent(
|
||||
condition: supportsSpark,
|
||||
builder: (child) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<bool>(
|
||||
value: _showSparkAddress,
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: true,
|
||||
child: Text(
|
||||
"Spark address",
|
||||
style: STextStyles.desktopTextMedium(context),
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: false,
|
||||
child: Text(
|
||||
"Transparent address",
|
||||
style: STextStyles.desktopTextMedium(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value is bool && value != _showSparkAddress) {
|
||||
setState(() {
|
||||
_showSparkAddress = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
isExpanded: true,
|
||||
iconStyleData: IconStyleData(
|
||||
icon: Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 12,
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconRight,
|
||||
),
|
||||
),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
offset: const Offset(0, -10),
|
||||
elevation: 0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
if (_showSparkAddress)
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
clipboard.setData(
|
||||
ClipboardData(text: receivingAddress),
|
||||
ClipboardData(text: _sparkAddress ?? "Error"),
|
||||
);
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
message: "Copied to clipboard",
|
||||
iconAsset: Assets.svg.copy,
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.backgroundAppBar,
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
child: RoundedWhiteContainer(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"Your ${widget.contractAddress == null ? coin.ticker : ref.watch(
|
||||
tokenServiceProvider.select(
|
||||
(value) => value!.tokenContract.symbol,
|
||||
),
|
||||
)} SPARK address",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
const Spacer(),
|
||||
Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.copy,
|
||||
width: 15,
|
||||
height: 15,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.infoItemIcons,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
Text(
|
||||
"Copy",
|
||||
style: STextStyles.link2(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
_sparkAddress ?? "Error",
|
||||
style:
|
||||
STextStyles.desktopTextExtraExtraSmall(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!_showSparkAddress) child,
|
||||
],
|
||||
),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
clipboard.setData(
|
||||
ClipboardData(
|
||||
text: ref.watch(pWalletReceivingAddress(walletId))),
|
||||
);
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.info,
|
||||
|
@ -179,9 +433,9 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
|
|||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
receivingAddress,
|
||||
style:
|
||||
STextStyles.desktopTextExtraExtraSmall(context)
|
||||
ref.watch(pWalletReceivingAddress(walletId)),
|
||||
style: STextStyles.desktopTextExtraExtraSmall(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
|
@ -197,26 +451,23 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (coin != Coin.epicCash &&
|
||||
coin != Coin.ethereum &&
|
||||
coin != Coin.banano &&
|
||||
coin != Coin.nano &&
|
||||
coin != Coin.stellar &&
|
||||
coin != Coin.stellarTestnet &&
|
||||
coin != Coin.tezos)
|
||||
),
|
||||
|
||||
if (ref.watch(pWallets.select((value) => value.getWallet(walletId)))
|
||||
is MultiAddressInterface ||
|
||||
supportsSpark)
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
if (coin != Coin.epicCash &&
|
||||
coin != Coin.ethereum &&
|
||||
coin != Coin.banano &&
|
||||
coin != Coin.nano &&
|
||||
coin != Coin.stellar &&
|
||||
coin != Coin.stellarTestnet &&
|
||||
coin != Coin.tezos)
|
||||
|
||||
if (ref.watch(pWallets.select((value) => value.getWallet(walletId)))
|
||||
is MultiAddressInterface ||
|
||||
supportsSpark)
|
||||
SecondaryButton(
|
||||
buttonHeight: ButtonHeight.l,
|
||||
onPressed: generateNewAddress,
|
||||
onPressed: supportsSpark && _showSparkAddress
|
||||
? generateNewSparkAddress
|
||||
: generateNewAddress,
|
||||
label: "Generate new address",
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -226,7 +477,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
|
|||
child: QrImageView(
|
||||
data: AddressUtils.buildUriString(
|
||||
coin,
|
||||
receivingAddress,
|
||||
_qrcodeContent ?? "",
|
||||
{},
|
||||
),
|
||||
size: 200,
|
||||
|
@ -267,7 +518,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
|
|||
RouteGenerator.generateRoute(
|
||||
RouteSettings(
|
||||
name: GenerateUriQrCodeView.routeName,
|
||||
arguments: Tuple2(coin, receivingAddress),
|
||||
arguments: Tuple2(coin, _qrcodeContent ?? ""),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -284,7 +535,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
|
|||
shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
|
||||
builder: (_) => GenerateUriQrCodeView(
|
||||
coin: coin,
|
||||
receivingAddress: receivingAddress,
|
||||
receivingAddress: _qrcodeContent ?? "",
|
||||
),
|
||||
settings: const RouteSettings(
|
||||
name: GenerateUriQrCodeView.routeName,
|
||||
|
|
|
@ -48,10 +48,12 @@ import 'package:stackwallet/utilities/logger.dart';
|
|||
import 'package:stackwallet/utilities/prefs.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
|
||||
import 'package:stackwallet/wallets/models/tx_data.dart';
|
||||
import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
import 'package:stackwallet/widgets/animated_text.dart';
|
||||
import 'package:stackwallet/widgets/conditional_parent.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||
|
@ -112,7 +114,6 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
String? _note;
|
||||
String? _onChainNote;
|
||||
|
||||
Amount? _amountToSend;
|
||||
Amount? _cachedAmountToSend;
|
||||
String? _address;
|
||||
|
||||
|
@ -137,20 +138,22 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
Future<void> previewSend() async {
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
|
||||
final Amount amount = _amountToSend!;
|
||||
final Amount amount = ref.read(pSendAmount)!;
|
||||
final Amount availableBalance;
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet)) {
|
||||
if (ref.read(publicPrivateBalanceStateProvider.state).state ==
|
||||
"Private") {
|
||||
switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
|
||||
case FiroType.public:
|
||||
availableBalance = wallet.info.cachedBalance.spendable;
|
||||
// (manager.wallet as FiroWallet).availablePrivateBalance();
|
||||
} else {
|
||||
break;
|
||||
case FiroType.lelantus:
|
||||
availableBalance = wallet.info.cachedBalanceSecondary.spendable;
|
||||
// (manager.wallet as FiroWallet).availablePublicBalance();
|
||||
break;
|
||||
case FiroType.spark:
|
||||
availableBalance = wallet.info.cachedBalanceTertiary.spendable;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
availableBalance = wallet.info.cachedBalance.spendable;
|
||||
;
|
||||
}
|
||||
|
||||
final coinControlEnabled =
|
||||
|
@ -312,14 +315,71 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
: null,
|
||||
),
|
||||
);
|
||||
} else if (wallet is FiroWallet &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state ==
|
||||
"Private") {
|
||||
} else if (wallet is FiroWallet) {
|
||||
switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
|
||||
case FiroType.public:
|
||||
if (ref.read(pValidSparkSendToAddress)) {
|
||||
txDataFuture = wallet.prepareSparkMintTransaction(
|
||||
txData: TxData(
|
||||
sparkRecipients: [
|
||||
(
|
||||
address: _address!,
|
||||
amount: amount,
|
||||
memo: memoController.text,
|
||||
)
|
||||
],
|
||||
feeRateType: ref.read(feeRateTypeStateProvider),
|
||||
satsPerVByte: isCustomFee ? customFeeRate : null,
|
||||
utxos: (wallet is CoinControlInterface &&
|
||||
coinControlEnabled &&
|
||||
ref.read(desktopUseUTXOs).isNotEmpty)
|
||||
? ref.read(desktopUseUTXOs)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
txDataFuture = wallet.prepareSend(
|
||||
txData: TxData(
|
||||
recipients: [(address: _address!, amount: amount)],
|
||||
feeRateType: ref.read(feeRateTypeStateProvider),
|
||||
satsPerVByte: isCustomFee ? customFeeRate : null,
|
||||
utxos: (wallet is CoinControlInterface &&
|
||||
coinControlEnabled &&
|
||||
ref.read(desktopUseUTXOs).isNotEmpty)
|
||||
? ref.read(desktopUseUTXOs)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case FiroType.lelantus:
|
||||
txDataFuture = wallet.prepareSendLelantus(
|
||||
txData: TxData(
|
||||
recipients: [(address: _address!, amount: amount)],
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
case FiroType.spark:
|
||||
txDataFuture = wallet.prepareSendSpark(
|
||||
txData: TxData(
|
||||
recipients: ref.read(pValidSparkSendToAddress)
|
||||
? null
|
||||
: [(address: _address!, amount: amount)],
|
||||
sparkRecipients: ref.read(pValidSparkSendToAddress)
|
||||
? [
|
||||
(
|
||||
address: _address!,
|
||||
amount: amount,
|
||||
memo: memoController.text,
|
||||
)
|
||||
]
|
||||
: null,
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
final memo = isStellar ? memoController.text : null;
|
||||
txDataFuture = wallet.prepareSend(
|
||||
|
@ -382,7 +442,8 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Desktop send: $e\n$s", level: LogLevel.Warning);
|
||||
if (mounted) {
|
||||
// pop building dialog
|
||||
Navigator.of(
|
||||
|
@ -469,21 +530,21 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse(
|
||||
cryptoAmountController.text,
|
||||
);
|
||||
final Amount? amount;
|
||||
if (cryptoAmount != null) {
|
||||
_amountToSend = cryptoAmount;
|
||||
if (_cachedAmountToSend != null &&
|
||||
_cachedAmountToSend == _amountToSend) {
|
||||
amount = cryptoAmount;
|
||||
if (_cachedAmountToSend != null && _cachedAmountToSend == amount) {
|
||||
return;
|
||||
}
|
||||
Logging.instance.log("it changed $_amountToSend $_cachedAmountToSend",
|
||||
Logging.instance.log("it changed $amount $_cachedAmountToSend",
|
||||
level: LogLevel.Info);
|
||||
_cachedAmountToSend = _amountToSend;
|
||||
_cachedAmountToSend = amount;
|
||||
|
||||
final price =
|
||||
ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1;
|
||||
|
||||
if (price > Decimal.zero) {
|
||||
final String fiatAmountString = (_amountToSend!.decimal * price)
|
||||
final String fiatAmountString = (amount!.decimal * price)
|
||||
.toAmount(fractionDigits: 2)
|
||||
.fiatString(
|
||||
locale: ref.read(localeServiceChangeNotifierProvider).locale,
|
||||
|
@ -492,44 +553,29 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
baseAmountController.text = fiatAmountString;
|
||||
}
|
||||
} else {
|
||||
_amountToSend = null;
|
||||
amount = null;
|
||||
_cachedAmountToSend = null;
|
||||
baseAmountController.text = "";
|
||||
}
|
||||
|
||||
_updatePreviewButtonState(_address, _amountToSend);
|
||||
ref.read(pSendAmount.notifier).state = amount;
|
||||
}
|
||||
}
|
||||
|
||||
String? _updateInvalidAddressText(String address) {
|
||||
if (_data != null && _data!.contactLabel == address) {
|
||||
return null;
|
||||
}
|
||||
if (address.isNotEmpty &&
|
||||
!ref
|
||||
.read(pWallets)
|
||||
.getWallet(walletId)
|
||||
.cryptoCurrency
|
||||
.validateAddress(address)) {
|
||||
return "Invalid address";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _updatePreviewButtonState(String? address, Amount? amount) {
|
||||
if (isPaynymSend) {
|
||||
ref.read(previewTxButtonStateProvider.state).state =
|
||||
(amount != null && amount > Amount.zero);
|
||||
} else {
|
||||
final isValidAddress = ref
|
||||
.read(pWallets)
|
||||
.getWallet(walletId)
|
||||
.cryptoCurrency
|
||||
.validateAddress(address ?? "");
|
||||
ref.read(previewTxButtonStateProvider.state).state =
|
||||
(isValidAddress && amount != null && amount > Amount.zero);
|
||||
}
|
||||
}
|
||||
// String? _updateInvalidAddressText(String address) {
|
||||
// if (_data != null && _data!.contactLabel == address) {
|
||||
// return null;
|
||||
// }
|
||||
// if (address.isNotEmpty &&
|
||||
// !ref
|
||||
// .read(pWallets)
|
||||
// .getWallet(walletId)
|
||||
// .cryptoCurrency
|
||||
// .validateAddress(address)) {
|
||||
// return "Invalid address";
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
|
||||
Future<void> scanQr() async {
|
||||
try {
|
||||
|
@ -567,10 +613,9 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
cryptoAmountController.text = ref
|
||||
.read(pAmountFormatter(coin))
|
||||
.format(amount, withUnitName: false);
|
||||
_amountToSend = amount;
|
||||
ref.read(pSendAmount.notifier).state = amount;
|
||||
}
|
||||
|
||||
_updatePreviewButtonState(_address, _amountToSend);
|
||||
setState(() {
|
||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||
});
|
||||
|
@ -584,7 +629,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
_address = qrResult.rawContent;
|
||||
sendToController.text = _address ?? "";
|
||||
|
||||
_updatePreviewButtonState(_address, _amountToSend);
|
||||
_setValidAddressProviders(_address);
|
||||
setState(() {
|
||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||
});
|
||||
|
@ -598,6 +643,25 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
}
|
||||
}
|
||||
|
||||
void _setValidAddressProviders(String? address) {
|
||||
if (isPaynymSend) {
|
||||
ref.read(pValidSendToAddress.notifier).state = true;
|
||||
} else {
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
if (wallet is SparkInterface) {
|
||||
ref.read(pValidSparkSendToAddress.notifier).state =
|
||||
SparkInterface.validateSparkAddress(
|
||||
address: address ?? "",
|
||||
isTestNet:
|
||||
wallet.cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
||||
);
|
||||
}
|
||||
|
||||
ref.read(pValidSendToAddress.notifier).state =
|
||||
wallet.cryptoCurrency.validateAddress(address ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> pasteAddress() async {
|
||||
final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain);
|
||||
if (data?.text != null && data!.text!.isNotEmpty) {
|
||||
|
@ -614,7 +678,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
sendToController.text = content;
|
||||
_address = content;
|
||||
|
||||
_updatePreviewButtonState(_address, _amountToSend);
|
||||
_setValidAddressProviders(_address);
|
||||
setState(() {
|
||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||
});
|
||||
|
@ -643,28 +707,29 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
baseAmountString,
|
||||
locale: ref.read(localeServiceChangeNotifierProvider).locale,
|
||||
);
|
||||
final Amount? amount;
|
||||
if (baseAmount != null) {
|
||||
final _price =
|
||||
ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1;
|
||||
|
||||
if (_price == Decimal.zero) {
|
||||
_amountToSend = Decimal.zero.toAmount(fractionDigits: coin.decimals);
|
||||
amount = Decimal.zero.toAmount(fractionDigits: coin.decimals);
|
||||
} else {
|
||||
_amountToSend = baseAmount <= Amount.zero
|
||||
amount = baseAmount <= Amount.zero
|
||||
? Decimal.zero.toAmount(fractionDigits: coin.decimals)
|
||||
: (baseAmount.decimal / _price)
|
||||
.toDecimal(scaleOnInfinitePrecision: coin.decimals)
|
||||
.toAmount(fractionDigits: coin.decimals);
|
||||
}
|
||||
if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) {
|
||||
if (_cachedAmountToSend != null && _cachedAmountToSend == amount) {
|
||||
return;
|
||||
}
|
||||
_cachedAmountToSend = _amountToSend;
|
||||
Logging.instance.log("it changed $_amountToSend $_cachedAmountToSend",
|
||||
level: LogLevel.Info);
|
||||
_cachedAmountToSend = amount;
|
||||
Logging.instance
|
||||
.log("it changed $amount $_cachedAmountToSend", level: LogLevel.Info);
|
||||
|
||||
final amountString = ref.read(pAmountFormatter(coin)).format(
|
||||
_amountToSend!,
|
||||
amount!,
|
||||
withUnitName: false,
|
||||
);
|
||||
|
||||
|
@ -672,7 +737,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
cryptoAmountController.text = amountString;
|
||||
_cryptoAmountChangeLock = false;
|
||||
} else {
|
||||
_amountToSend = Decimal.zero.toAmount(fractionDigits: coin.decimals);
|
||||
amount = Decimal.zero.toAmount(fractionDigits: coin.decimals);
|
||||
_cryptoAmountChangeLock = true;
|
||||
cryptoAmountController.text = "";
|
||||
_cryptoAmountChangeLock = false;
|
||||
|
@ -682,17 +747,29 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
// Format.decimalAmountToSatoshis(
|
||||
// _amountToSend!));
|
||||
// });
|
||||
_updatePreviewButtonState(_address, _amountToSend);
|
||||
ref.read(pSendAmount.notifier).state = amount;
|
||||
}
|
||||
|
||||
Future<void> sendAllTapped() async {
|
||||
final info = ref.read(pWalletInfo(walletId));
|
||||
|
||||
if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state == "Private") {
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
|
||||
case FiroType.public:
|
||||
cryptoAmountController.text = info.cachedBalance.spendable.decimal
|
||||
.toStringAsFixed(coin.decimals);
|
||||
break;
|
||||
case FiroType.lelantus:
|
||||
cryptoAmountController.text = info
|
||||
.cachedBalanceSecondary.spendable.decimal
|
||||
.toStringAsFixed(coin.decimals);
|
||||
break;
|
||||
case FiroType.spark:
|
||||
cryptoAmountController.text = info
|
||||
.cachedBalanceTertiary.spendable.decimal
|
||||
.toStringAsFixed(coin.decimals);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
cryptoAmountController.text =
|
||||
info.cachedBalance.spendable.decimal.toStringAsFixed(coin.decimals);
|
||||
|
@ -700,11 +777,12 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
}
|
||||
|
||||
void _showDesktopCoinControl() async {
|
||||
final amount = ref.read(pSendAmount);
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => DesktopCoinControlUseDialog(
|
||||
walletId: widget.walletId,
|
||||
amountToSend: _amountToSend,
|
||||
amountToSend: amount,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -713,7 +791,8 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
void initState() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
ref.refresh(feeSheetSessionCacheProvider);
|
||||
ref.read(previewTxButtonStateProvider.state).state = false;
|
||||
ref.read(pValidSendToAddress.state).state = false;
|
||||
ref.read(pValidSparkSendToAddress.state).state = false;
|
||||
});
|
||||
|
||||
// _calculateFeesFuture = calculateFees(0);
|
||||
|
@ -748,20 +827,20 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
|
||||
_cryptoFocus.addListener(() {
|
||||
if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) {
|
||||
if (_amountToSend == null) {
|
||||
if (ref.read(pSendAmount) == null) {
|
||||
ref.refresh(sendAmountProvider);
|
||||
} else {
|
||||
ref.read(sendAmountProvider.state).state = _amountToSend!;
|
||||
ref.read(sendAmountProvider.state).state = ref.read(pSendAmount)!;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_baseFocus.addListener(() {
|
||||
if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) {
|
||||
if (_amountToSend == null) {
|
||||
if (ref.read(pSendAmount) == null) {
|
||||
ref.refresh(sendAmountProvider);
|
||||
} else {
|
||||
ref.read(sendAmountProvider.state).state = _amountToSend!;
|
||||
ref.read(sendAmountProvider.state).state = ref.read(pSendAmount)!;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -821,7 +900,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
if (coin == Coin.firo)
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet)
|
||||
Text(
|
||||
"Send from",
|
||||
style: STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
|
@ -831,22 +910,42 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
if (coin == Coin.firo)
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet)
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
if (coin == Coin.firo)
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet)
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2(
|
||||
isExpanded: true,
|
||||
value: ref.watch(publicPrivateBalanceStateProvider.state).state,
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: "Private",
|
||||
value: FiroType.spark,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"Private balance",
|
||||
"Spark balance",
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text(
|
||||
ref.watch(pAmountFormatter(coin)).format(ref
|
||||
.watch(pWalletBalanceTertiary(walletId))
|
||||
.spendable),
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: FiroType.lelantus,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"Lelantus balance",
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
const SizedBox(
|
||||
|
@ -862,7 +961,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: "Public",
|
||||
value: FiroType.public,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
|
@ -882,9 +981,9 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value is String) {
|
||||
if (value is FiroType) {
|
||||
setState(() {
|
||||
ref.watch(publicPrivateBalanceStateProvider.state).state =
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state =
|
||||
value;
|
||||
});
|
||||
}
|
||||
|
@ -917,7 +1016,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (coin == Coin.firo)
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet)
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
@ -1159,7 +1258,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
),
|
||||
onChanged: (newValue) {
|
||||
_address = newValue;
|
||||
_updatePreviewButtonState(_address, _amountToSend);
|
||||
_setValidAddressProviders(_address);
|
||||
|
||||
setState(() {
|
||||
_addressToggleFlag = newValue.isNotEmpty;
|
||||
|
@ -1199,8 +1298,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
onTap: () {
|
||||
sendToController.text = "";
|
||||
_address = "";
|
||||
_updatePreviewButtonState(
|
||||
_address, _amountToSend);
|
||||
_setValidAddressProviders(_address);
|
||||
setState(() {
|
||||
_addressToggleFlag = false;
|
||||
});
|
||||
|
@ -1261,10 +1359,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
|
||||
_address = entry.address;
|
||||
|
||||
_updatePreviewButtonState(
|
||||
_address,
|
||||
_amountToSend,
|
||||
);
|
||||
_setValidAddressProviders(_address);
|
||||
|
||||
setState(() {
|
||||
_addressToggleFlag = true;
|
||||
|
@ -1289,9 +1384,44 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
if (!isPaynymSend)
|
||||
Builder(
|
||||
builder: (_) {
|
||||
final error = _updateInvalidAddressText(
|
||||
_address ?? "",
|
||||
);
|
||||
final String? error;
|
||||
|
||||
if (_address == null || _address!.isEmpty) {
|
||||
error = null;
|
||||
} else if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
if (ref.watch(publicPrivateBalanceStateProvider) ==
|
||||
FiroType.lelantus) {
|
||||
if (_data != null && _data!.contactLabel == _address) {
|
||||
error = SparkInterface.validateSparkAddress(
|
||||
address: _data!.address, isTestNet: coin.isTestNet)
|
||||
? "Lelantus to Spark not supported"
|
||||
: null;
|
||||
} else if (ref.watch(pValidSparkSendToAddress)) {
|
||||
error = "Lelantus to Spark not supported";
|
||||
} else {
|
||||
error = ref.watch(pValidSendToAddress)
|
||||
? null
|
||||
: "Invalid address";
|
||||
}
|
||||
} else {
|
||||
if (_data != null && _data!.contactLabel == _address) {
|
||||
error = null;
|
||||
} else if (!ref.watch(pValidSendToAddress) &&
|
||||
!ref.watch(pValidSparkSendToAddress)) {
|
||||
error = "Invalid address";
|
||||
} else {
|
||||
error = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_data != null && _data!.contactLabel == _address) {
|
||||
error = null;
|
||||
} else if (!ref.watch(pValidSendToAddress)) {
|
||||
error = "Invalid address";
|
||||
} else {
|
||||
error = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (error == null || error.isEmpty) {
|
||||
return Container();
|
||||
|
@ -1317,11 +1447,17 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
}
|
||||
},
|
||||
),
|
||||
if (isStellar)
|
||||
if (isStellar ||
|
||||
(ref.watch(pValidSparkSendToAddress) &&
|
||||
ref.watch(publicPrivateBalanceStateProvider) !=
|
||||
FiroType.lelantus))
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
if (isStellar)
|
||||
if (isStellar ||
|
||||
(ref.watch(pValidSparkSendToAddress) &&
|
||||
ref.watch(publicPrivateBalanceStateProvider) !=
|
||||
FiroType.lelantus))
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
|
@ -1387,8 +1523,12 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
ConditionalParent(
|
||||
condition: coin.isElectrumXCoin &&
|
||||
!(((coin == Coin.firo || coin == Coin.firoTestNet) &&
|
||||
ref.read(publicPrivateBalanceStateProvider.state).state ==
|
||||
"Private")),
|
||||
(ref.watch(publicPrivateBalanceStateProvider.state).state ==
|
||||
FiroType.lelantus ||
|
||||
ref
|
||||
.watch(publicPrivateBalanceStateProvider.state)
|
||||
.state ==
|
||||
FiroType.spark))),
|
||||
builder: (child) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
|
@ -1487,14 +1627,32 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state !=
|
||||
"Private") {
|
||||
throw UnimplementedError("FIXME");
|
||||
// TODO: [prio=high] firo fee fix
|
||||
// ref
|
||||
// .read(feeSheetSessionCacheProvider)
|
||||
// .average[amount] = await (manager.wallet
|
||||
// as FiroWallet)
|
||||
// .estimateFeeForPublic(amount, feeRate);
|
||||
FiroType.public) {
|
||||
final firoWallet = wallet as FiroWallet;
|
||||
|
||||
if (ref
|
||||
.read(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state ==
|
||||
FiroType.lelantus) {
|
||||
ref
|
||||
.read(feeSheetSessionCacheProvider)
|
||||
.average[amount] =
|
||||
await firoWallet
|
||||
.estimateFeeForLelantus(amount);
|
||||
} else if (ref
|
||||
.read(
|
||||
publicPrivateBalanceStateProvider
|
||||
.state)
|
||||
.state ==
|
||||
FiroType.spark) {
|
||||
ref
|
||||
.read(feeSheetSessionCacheProvider)
|
||||
.average[amount] =
|
||||
await firoWallet
|
||||
.estimateFeeForSpark(amount);
|
||||
}
|
||||
} else {
|
||||
ref
|
||||
.read(feeSheetSessionCacheProvider)
|
||||
|
@ -1532,7 +1690,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
.watch(
|
||||
publicPrivateBalanceStateProvider.state)
|
||||
.state ==
|
||||
"Private"
|
||||
FiroType.lelantus
|
||||
? Text(
|
||||
"~${ref.watch(pAmountFormatter(coin)).format(
|
||||
Amount(
|
||||
|
@ -1595,10 +1753,9 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
PrimaryButton(
|
||||
buttonHeight: ButtonHeight.l,
|
||||
label: "Preview send",
|
||||
enabled: ref.watch(previewTxButtonStateProvider.state).state,
|
||||
onPressed: ref.watch(previewTxButtonStateProvider.state).state
|
||||
? previewSend
|
||||
: null,
|
||||
enabled: ref.watch(pPreviewTxButtonEnabled(coin)),
|
||||
onPressed:
|
||||
ref.watch(pPreviewTxButtonEnabled(coin)) ? previewSend : null,
|
||||
)
|
||||
],
|
||||
);
|
||||
|
|
|
@ -198,7 +198,8 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
}
|
||||
|
||||
try {
|
||||
await firoWallet.anonymizeAllPublicFunds();
|
||||
// await firoWallet.anonymizeAllLelantus();
|
||||
await firoWallet.anonymizeAllSpark();
|
||||
shouldPop = true;
|
||||
if (context.mounted) {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
|
|
|
@ -15,6 +15,7 @@ import 'package:stackwallet/pages/token_view/token_view.dart';
|
|||
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_refresh_button.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart';
|
||||
import 'package:stackwallet/providers/providers.dart';
|
||||
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
||||
import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
|
@ -61,6 +62,7 @@ class _WDesktopWalletSummaryState extends ConsumerState<DesktopWalletSummary> {
|
|||
),
|
||||
);
|
||||
final coin = ref.watch(pWalletCoin(widget.walletId));
|
||||
final isFiro = coin == Coin.firo || coin == Coin.firoTestNet;
|
||||
final locale = ref.watch(
|
||||
localeServiceChangeNotifierProvider.select((value) => value.locale));
|
||||
|
||||
|
@ -82,29 +84,30 @@ class _WDesktopWalletSummaryState extends ConsumerState<DesktopWalletSummary> {
|
|||
ref.watch(walletBalanceToggleStateProvider.state).state ==
|
||||
WalletBalanceToggleState.available;
|
||||
|
||||
final Amount balanceToShow;
|
||||
if (isFiro) {
|
||||
switch (ref.watch(publicPrivateBalanceStateProvider.state).state) {
|
||||
case FiroType.spark:
|
||||
final balance = ref.watch(pWalletBalanceTertiary(walletId));
|
||||
balanceToShow = _showAvailable ? balance.spendable : balance.total;
|
||||
break;
|
||||
|
||||
case FiroType.lelantus:
|
||||
final balance = ref.watch(pWalletBalanceSecondary(walletId));
|
||||
balanceToShow = _showAvailable ? balance.spendable : balance.total;
|
||||
break;
|
||||
|
||||
case FiroType.public:
|
||||
final balance = ref.watch(pWalletBalance(walletId));
|
||||
balanceToShow = _showAvailable ? balance.spendable : balance.total;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
Balance balance = widget.isToken
|
||||
? ref.watch(tokenServiceProvider.select((value) => value!.balance))
|
||||
: ref.watch(pWalletBalance(walletId));
|
||||
|
||||
Amount balanceToShow;
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
final balanceSecondary = ref.watch(pWalletBalanceSecondary(walletId));
|
||||
final showPrivate =
|
||||
ref.watch(walletPrivateBalanceToggleStateProvider.state).state ==
|
||||
WalletBalanceToggleState.available;
|
||||
|
||||
if (_showAvailable) {
|
||||
balanceToShow =
|
||||
showPrivate ? balanceSecondary.spendable : balance.spendable;
|
||||
} else {
|
||||
balanceToShow = showPrivate ? balanceSecondary.total : balance.total;
|
||||
}
|
||||
} else {
|
||||
if (_showAvailable) {
|
||||
balanceToShow = balance.spendable;
|
||||
} else {
|
||||
balanceToShow = balance.total;
|
||||
}
|
||||
balanceToShow = _showAvailable ? balance.spendable : balance.total;
|
||||
}
|
||||
|
||||
return Consumer(
|
||||
|
|
|
@ -19,6 +19,7 @@ import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_set
|
|||
import 'package:stackwallet/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/lelantus_coins/lelantus_coins_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/spark_coins/spark_coins_view.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
|
@ -32,7 +33,8 @@ enum _WalletOptions {
|
|||
deleteWallet,
|
||||
changeRepresentative,
|
||||
showXpub,
|
||||
lelantusCoins;
|
||||
lelantusCoins,
|
||||
sparkCoins;
|
||||
|
||||
String get prettyName {
|
||||
switch (this) {
|
||||
|
@ -46,6 +48,8 @@ enum _WalletOptions {
|
|||
return "Show xPub";
|
||||
case _WalletOptions.lelantusCoins:
|
||||
return "Lelantus Coins";
|
||||
case _WalletOptions.sparkCoins:
|
||||
return "Spark Coins";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +93,9 @@ class WalletOptionsButton extends StatelessWidget {
|
|||
onFiroShowLelantusCoins: () async {
|
||||
Navigator.of(context).pop(_WalletOptions.lelantusCoins);
|
||||
},
|
||||
onFiroShowSparkCoins: () async {
|
||||
Navigator.of(context).pop(_WalletOptions.sparkCoins);
|
||||
},
|
||||
walletId: walletId,
|
||||
);
|
||||
},
|
||||
|
@ -191,6 +198,15 @@ class WalletOptionsButton extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
break;
|
||||
|
||||
case _WalletOptions.sparkCoins:
|
||||
unawaited(
|
||||
Navigator.of(context).pushNamed(
|
||||
SparkCoinsView.routeName,
|
||||
arguments: walletId,
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -224,6 +240,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget {
|
|||
required this.onShowXpubPressed,
|
||||
required this.onChangeRepPressed,
|
||||
required this.onFiroShowLelantusCoins,
|
||||
required this.onFiroShowSparkCoins,
|
||||
required this.walletId,
|
||||
}) : super(key: key);
|
||||
|
||||
|
@ -232,6 +249,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget {
|
|||
final VoidCallback onShowXpubPressed;
|
||||
final VoidCallback onChangeRepPressed;
|
||||
final VoidCallback onFiroShowLelantusCoins;
|
||||
final VoidCallback onFiroShowSparkCoins;
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
|
@ -374,6 +392,43 @@ class WalletOptionsPopupMenu extends ConsumerWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (firoDebug)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (firoDebug)
|
||||
TransparentButton(
|
||||
onPressed: onFiroShowSparkCoins,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.eye,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconLeft,
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_WalletOptions.sparkCoins.prettyName,
|
||||
style: STextStyles.desktopTextExtraExtraSmall(
|
||||
context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (xpubEnabled)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
|
|
|
@ -65,7 +65,14 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
|
|||
bool get fieldsMatch =>
|
||||
passwordController.text == passwordRepeatController.text;
|
||||
|
||||
bool _nextLock = false;
|
||||
|
||||
void onNextPressed() async {
|
||||
if (_nextLock) {
|
||||
return;
|
||||
}
|
||||
_nextLock = true;
|
||||
|
||||
final String passphrase = passwordController.text;
|
||||
final String repeatPassphrase = passwordRepeatController.text;
|
||||
|
||||
|
@ -75,6 +82,7 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
|
|||
message: "A password is required",
|
||||
context: context,
|
||||
));
|
||||
_nextLock = false;
|
||||
return;
|
||||
}
|
||||
if (passphrase != repeatPassphrase) {
|
||||
|
@ -83,6 +91,7 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
|
|||
message: "Password does not match",
|
||||
context: context,
|
||||
));
|
||||
_nextLock = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -106,6 +115,7 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
|
|||
message: "Error: $e",
|
||||
context: context,
|
||||
));
|
||||
_nextLock = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -132,6 +142,7 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
|
|||
context: context,
|
||||
));
|
||||
}
|
||||
_nextLock = false;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -79,11 +79,18 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
|
|||
}
|
||||
}
|
||||
|
||||
bool _loginLock = false;
|
||||
Future<void> login() async {
|
||||
if (_loginLock) {
|
||||
return;
|
||||
}
|
||||
_loginLock = true;
|
||||
|
||||
try {
|
||||
unawaited(
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => const Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
|
@ -138,6 +145,8 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
|
|||
context: context,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_loginLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
267
lib/pages_desktop_specific/spark_coins/spark_coins_view.dart
Normal file
267
lib/pages_desktop_specific/spark_coins/spark_coins_view.dart
Normal file
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
* This file is part of Stack Wallet.
|
||||
*
|
||||
* Copyright (c) 2023 Cypher Stack
|
||||
* All Rights Reserved.
|
||||
* The code is distributed under GPLv3 license, see LICENSE file for details.
|
||||
* Generated by Cypher Stack on 2023-05-26
|
||||
*
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/providers/db/main_db_provider.dart';
|
||||
import 'package:stackwallet/themes/stack_colors.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/wallets/isar/models/spark_coin.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
|
||||
class SparkCoinsView extends ConsumerStatefulWidget {
|
||||
const SparkCoinsView({
|
||||
Key? key,
|
||||
required this.walletId,
|
||||
}) : super(key: key);
|
||||
|
||||
static const String routeName = "/sparkCoinsView";
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<SparkCoinsView> createState() => _SparkCoinsViewState();
|
||||
}
|
||||
|
||||
class _SparkCoinsViewState extends ConsumerState<SparkCoinsView> {
|
||||
List<SparkCoin> _coins = [];
|
||||
|
||||
Stream<List<SparkCoin>>? sparkCoinsCollectionWatcher;
|
||||
|
||||
void _onSparkCoinsCollectionWatcherEvent(List<SparkCoin> coins) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_coins = coins;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
sparkCoinsCollectionWatcher = ref
|
||||
.read(mainDBProvider)
|
||||
.isar
|
||||
.sparkCoins
|
||||
.where()
|
||||
.walletIdEqualToAnyLTagHash(widget.walletId)
|
||||
.sortByHeightDesc()
|
||||
.watch(fireImmediately: true);
|
||||
sparkCoinsCollectionWatcher!
|
||||
.listen((data) => _onSparkCoinsCollectionWatcherEvent(data));
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
sparkCoinsCollectionWatcher = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DesktopScaffold(
|
||||
appBar: DesktopAppBar(
|
||||
background: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
leading: Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 32,
|
||||
),
|
||||
AppBarIconButton(
|
||||
size: 32,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
shadows: const [],
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.arrowLeft,
|
||||
width: 18,
|
||||
height: 18,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
"Spark Coins",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
useSpacers: false,
|
||||
isCompactHeight: true,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: RoundedWhiteContainer(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 9,
|
||||
child: Text(
|
||||
"TXID",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 9,
|
||||
child: Text(
|
||||
"LTag Hash",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text(
|
||||
"Value (sats)",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
"Height",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
"Group Id",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
"Type",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
"Used",
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: _coins.length,
|
||||
separatorBuilder: (_, __) => Container(
|
||||
height: 1,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.backgroundAppBar,
|
||||
),
|
||||
itemBuilder: (_, index) => Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: RoundedWhiteContainer(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 9,
|
||||
child: SelectableText(
|
||||
_coins[index].txHash,
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 9,
|
||||
child: SelectableText(
|
||||
_coins[index].lTagHash,
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: SelectableText(
|
||||
_coins[index].value.toString(),
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SelectableText(
|
||||
_coins[index].height.toString(),
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SelectableText(
|
||||
_coins[index].groupId.toString(),
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SelectableText(
|
||||
_coins[index].type.name,
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: SelectableText(
|
||||
_coins[index].isUsed.toString(),
|
||||
style: STextStyles.itemSubtitle12(context),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -9,9 +9,32 @@
|
|||
*/
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
|
||||
final previewTxButtonStateProvider = StateProvider.autoDispose<bool>((_) {
|
||||
return false;
|
||||
final pSendAmount = StateProvider.autoDispose<Amount?>((_) => null);
|
||||
final pValidSendToAddress = StateProvider.autoDispose<bool>((_) => false);
|
||||
final pValidSparkSendToAddress = StateProvider.autoDispose<bool>((_) => false);
|
||||
|
||||
final pPreviewTxButtonEnabled =
|
||||
Provider.autoDispose.family<bool, Coin>((ref, coin) {
|
||||
final amount = ref.watch(pSendAmount) ?? Amount.zero;
|
||||
|
||||
// TODO [prio=low]: move away from Coin
|
||||
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||
if (ref.watch(publicPrivateBalanceStateProvider) == FiroType.lelantus) {
|
||||
return ref.watch(pValidSendToAddress) &&
|
||||
!ref.watch(pValidSparkSendToAddress) &&
|
||||
amount > Amount.zero;
|
||||
} else {
|
||||
return (ref.watch(pValidSendToAddress) ||
|
||||
ref.watch(pValidSparkSendToAddress)) &&
|
||||
amount > Amount.zero;
|
||||
}
|
||||
} else {
|
||||
return ref.watch(pValidSendToAddress) && amount > Amount.zero;
|
||||
}
|
||||
});
|
||||
|
||||
final previewTokenTxButtonStateProvider = StateProvider.autoDispose<bool>((_) {
|
||||
|
|
|
@ -10,5 +10,11 @@
|
|||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
enum FiroType {
|
||||
public,
|
||||
lelantus,
|
||||
spark;
|
||||
}
|
||||
|
||||
final publicPrivateBalanceStateProvider =
|
||||
StateProvider<String>((_) => "Private");
|
||||
StateProvider<FiroType>((_) => FiroType.lelantus);
|
||||
|
|
|
@ -14,7 +14,3 @@ import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
|
|||
final walletBalanceToggleStateProvider =
|
||||
StateProvider.autoDispose<WalletBalanceToggleState>(
|
||||
(ref) => WalletBalanceToggleState.full);
|
||||
|
||||
final walletPrivateBalanceToggleStateProvider =
|
||||
StateProvider.autoDispose<WalletBalanceToggleState>(
|
||||
(ref) => WalletBalanceToggleState.full);
|
||||
|
|
|
@ -175,6 +175,7 @@ import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/nodes_
|
|||
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/security_settings.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/syncing_preferences_settings.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/tor_settings/tor_settings.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/spark_coins/spark_coins_view.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
|
||||
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
|
@ -1858,6 +1859,20 @@ class RouteGenerator {
|
|||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case SparkCoinsView.routeName:
|
||||
if (args is String) {
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => SparkCoinsView(
|
||||
walletId: args,
|
||||
),
|
||||
settings: RouteSettings(
|
||||
name: settings.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
||||
|
||||
case DesktopCoinControlView.routeName:
|
||||
if (args is String) {
|
||||
return getRoute(
|
||||
|
|
|
@ -611,7 +611,8 @@ abstract class EthereumAPI {
|
|||
try {
|
||||
final response = await client.get(
|
||||
url: Uri.parse(
|
||||
"$stackBaseServer/tokens?addrs=$contractAddress&parts=all",
|
||||
// "$stackBaseServer/tokens?addrs=$contractAddress&parts=all",
|
||||
"$stackBaseServer/names?terms=$contractAddress",
|
||||
),
|
||||
proxyInfo: Prefs.instance.useTor
|
||||
? TorService.sharedInstance.getProxyInfo()
|
||||
|
@ -621,6 +622,10 @@ abstract class EthereumAPI {
|
|||
if (response.code == 200) {
|
||||
final json = jsonDecode(response.body) as Map;
|
||||
if (json["data"] is List) {
|
||||
if ((json["data"] as List).isEmpty) {
|
||||
throw EthApiException("Unknown token");
|
||||
}
|
||||
|
||||
final map = Map<String, dynamic>.from(json["data"].first as Map);
|
||||
EthContract? token;
|
||||
if (map["isErc20"] == true) {
|
||||
|
|
|
@ -113,6 +113,7 @@ mixin ElectrumXParsing {
|
|||
outputs: List.unmodifiable(outputs),
|
||||
subType: TransactionSubType.none,
|
||||
type: TransactionType.unknown,
|
||||
otherData: null,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -35,8 +35,6 @@ class Wallets {
|
|||
late NodeService nodeService;
|
||||
late MainDB mainDB;
|
||||
|
||||
bool get hasWallets => _wallets.isNotEmpty;
|
||||
|
||||
List<Wallet> get wallets => _wallets.values.toList();
|
||||
|
||||
static bool hasLoaded = false;
|
||||
|
|
|
@ -26,6 +26,10 @@ class Amount {
|
|||
fractionDigits: 0,
|
||||
);
|
||||
|
||||
Amount.zeroWith({required this.fractionDigits})
|
||||
: assert(fractionDigits >= 0),
|
||||
_value = BigInt.zero;
|
||||
|
||||
/// truncate decimal value to [fractionDigits] places
|
||||
Amount.fromDecimal(Decimal amount, {required this.fractionDigits})
|
||||
: assert(fractionDigits >= 0),
|
||||
|
|
|
@ -42,7 +42,7 @@ enum Coin {
|
|||
stellarTestnet,
|
||||
}
|
||||
|
||||
final int kTestNetCoinCount = 5; // Util.isDesktop ? 5 : 4;
|
||||
final int kTestNetCoinCount = 6; // Util.isDesktop ? 5 : 4;
|
||||
// remove firotestnet for now
|
||||
|
||||
extension CoinExt on Coin {
|
||||
|
|
|
@ -20,16 +20,18 @@ abstract class StackFileSystem {
|
|||
static Future<Directory> applicationRootDirectory() async {
|
||||
Directory appDirectory;
|
||||
|
||||
// if this is changed, the directories in libmonero must also be changed!!!!!
|
||||
const dirName = "stackwallet";
|
||||
|
||||
// todo: can merge and do same as regular linux home dir?
|
||||
if (Logging.isArmLinux) {
|
||||
appDirectory = await getApplicationDocumentsDirectory();
|
||||
appDirectory = Directory("${appDirectory.path}/.stackwallet");
|
||||
appDirectory = Directory("${appDirectory.path}/.$dirName");
|
||||
} else if (Platform.isLinux) {
|
||||
if (overrideDir != null) {
|
||||
appDirectory = Directory(overrideDir!);
|
||||
} else {
|
||||
appDirectory =
|
||||
Directory("${Platform.environment['HOME']}/.stackwallet");
|
||||
appDirectory = Directory("${Platform.environment['HOME']}/.$dirName");
|
||||
}
|
||||
} else if (Platform.isWindows) {
|
||||
if (overrideDir != null) {
|
||||
|
@ -42,7 +44,7 @@ abstract class StackFileSystem {
|
|||
appDirectory = Directory(overrideDir!);
|
||||
} else {
|
||||
appDirectory = await getLibraryDirectory();
|
||||
appDirectory = Directory("${appDirectory.path}/stackwallet");
|
||||
appDirectory = Directory("${appDirectory.path}/$dirName");
|
||||
}
|
||||
} else if (Platform.isIOS) {
|
||||
// todo: check if we need different behaviour here
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
|||
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_hd_currency.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
|
||||
class Firo extends Bip39HDCurrency {
|
||||
Firo(super.network) {
|
||||
|
@ -132,9 +133,15 @@ class Firo extends Bip39HDCurrency {
|
|||
coinlib.Address.fromString(address, networkParams);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
return validateSparkAddress(address);
|
||||
}
|
||||
// TODO: implement validateAddress for spark addresses?
|
||||
}
|
||||
|
||||
bool validateSparkAddress(String address) {
|
||||
return SparkInterface.validateSparkAddress(
|
||||
address: address,
|
||||
isTestNet: network == CryptoCurrencyNetwork.test,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
148
lib/wallets/isar/models/spark_coin.dart
Normal file
148
lib/wallets/isar/models/spark_coin.dart
Normal file
|
@ -0,0 +1,148 @@
|
|||
import 'package:isar/isar.dart';
|
||||
|
||||
part 'spark_coin.g.dart';
|
||||
|
||||
enum SparkCoinType {
|
||||
mint(0),
|
||||
spend(1);
|
||||
|
||||
const SparkCoinType(this.value);
|
||||
|
||||
final int value;
|
||||
}
|
||||
|
||||
@Collection()
|
||||
class SparkCoin {
|
||||
Id id = Isar.autoIncrement;
|
||||
|
||||
@Index(
|
||||
unique: true,
|
||||
replace: true,
|
||||
composite: [
|
||||
CompositeIndex("lTagHash"),
|
||||
],
|
||||
)
|
||||
final String walletId;
|
||||
|
||||
@enumerated
|
||||
final SparkCoinType type;
|
||||
|
||||
final bool isUsed;
|
||||
final int groupId;
|
||||
|
||||
final List<int>? nonce;
|
||||
|
||||
final String address;
|
||||
final String txHash;
|
||||
|
||||
final String valueIntString;
|
||||
|
||||
final String? memo;
|
||||
final List<int>? serialContext;
|
||||
|
||||
final String diversifierIntString;
|
||||
final List<int>? encryptedDiversifier;
|
||||
|
||||
final List<int>? serial;
|
||||
final List<int>? tag;
|
||||
|
||||
final String lTagHash;
|
||||
|
||||
final int? height;
|
||||
|
||||
final String? serializedCoinB64;
|
||||
final String? contextB64;
|
||||
|
||||
@ignore
|
||||
BigInt get value => BigInt.parse(valueIntString);
|
||||
|
||||
@ignore
|
||||
BigInt get diversifier => BigInt.parse(diversifierIntString);
|
||||
|
||||
SparkCoin({
|
||||
required this.walletId,
|
||||
required this.type,
|
||||
required this.isUsed,
|
||||
required this.groupId,
|
||||
this.nonce,
|
||||
required this.address,
|
||||
required this.txHash,
|
||||
required this.valueIntString,
|
||||
this.memo,
|
||||
this.serialContext,
|
||||
required this.diversifierIntString,
|
||||
this.encryptedDiversifier,
|
||||
this.serial,
|
||||
this.tag,
|
||||
required this.lTagHash,
|
||||
this.height,
|
||||
this.serializedCoinB64,
|
||||
this.contextB64,
|
||||
});
|
||||
|
||||
SparkCoin copyWith({
|
||||
SparkCoinType? type,
|
||||
bool? isUsed,
|
||||
int? groupId,
|
||||
List<int>? nonce,
|
||||
String? address,
|
||||
String? txHash,
|
||||
BigInt? value,
|
||||
String? memo,
|
||||
List<int>? serialContext,
|
||||
BigInt? diversifier,
|
||||
List<int>? encryptedDiversifier,
|
||||
List<int>? serial,
|
||||
List<int>? tag,
|
||||
String? lTagHash,
|
||||
int? height,
|
||||
String? serializedCoinB64,
|
||||
String? contextB64,
|
||||
}) {
|
||||
return SparkCoin(
|
||||
walletId: walletId,
|
||||
type: type ?? this.type,
|
||||
isUsed: isUsed ?? this.isUsed,
|
||||
groupId: groupId ?? this.groupId,
|
||||
nonce: nonce ?? this.nonce,
|
||||
address: address ?? this.address,
|
||||
txHash: txHash ?? this.txHash,
|
||||
valueIntString: value?.toString() ?? this.value.toString(),
|
||||
memo: memo ?? this.memo,
|
||||
serialContext: serialContext ?? this.serialContext,
|
||||
diversifierIntString:
|
||||
diversifier?.toString() ?? this.diversifier.toString(),
|
||||
encryptedDiversifier: encryptedDiversifier ?? this.encryptedDiversifier,
|
||||
serial: serial ?? this.serial,
|
||||
tag: tag ?? this.tag,
|
||||
lTagHash: lTagHash ?? this.lTagHash,
|
||||
height: height ?? this.height,
|
||||
serializedCoinB64: serializedCoinB64 ?? this.serializedCoinB64,
|
||||
contextB64: contextB64 ?? this.contextB64,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SparkCoin('
|
||||
'walletId: $walletId'
|
||||
', type: $type'
|
||||
', isUsed: $isUsed'
|
||||
', groupId: $groupId'
|
||||
', k: $nonce'
|
||||
', address: $address'
|
||||
', txHash: $txHash'
|
||||
', value: $value'
|
||||
', memo: $memo'
|
||||
', serialContext: $serialContext'
|
||||
', diversifier: $diversifier'
|
||||
', encryptedDiversifier: $encryptedDiversifier'
|
||||
', serial: $serial'
|
||||
', tag: $tag'
|
||||
', lTagHash: $lTagHash'
|
||||
', height: $height'
|
||||
', serializedCoinB64: $serializedCoinB64'
|
||||
', contextB64: $contextB64'
|
||||
')';
|
||||
}
|
||||
}
|
3456
lib/wallets/isar/models/spark_coin.g.dart
Normal file
3456
lib/wallets/isar/models/spark_coin.g.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -55,6 +55,15 @@ class TxData {
|
|||
// tezos specific
|
||||
final tezart.OperationsList? tezosOperationsList;
|
||||
|
||||
// firo spark specific
|
||||
final List<
|
||||
({
|
||||
String address,
|
||||
Amount amount,
|
||||
String memo,
|
||||
})>? sparkRecipients;
|
||||
final List<TxData>? sparkMints;
|
||||
|
||||
TxData({
|
||||
this.feeRateType,
|
||||
this.feeRateAmount,
|
||||
|
@ -85,6 +94,8 @@ class TxData {
|
|||
this.txSubType,
|
||||
this.mintsMapLelantus,
|
||||
this.tezosOperationsList,
|
||||
this.sparkRecipients,
|
||||
this.sparkMints,
|
||||
});
|
||||
|
||||
Amount? get amount => recipients != null && recipients!.isNotEmpty
|
||||
|
@ -93,6 +104,13 @@ class TxData {
|
|||
.reduce((total, amount) => total += amount)
|
||||
: null;
|
||||
|
||||
Amount? get amountSpark =>
|
||||
sparkRecipients != null && sparkRecipients!.isNotEmpty
|
||||
? sparkRecipients!
|
||||
.map((e) => e.amount)
|
||||
.reduce((total, amount) => total += amount)
|
||||
: null;
|
||||
|
||||
int? get estimatedSatsPerVByte => fee != null && vSize != null
|
||||
? (fee!.raw ~/ BigInt.from(vSize!)).toInt()
|
||||
: null;
|
||||
|
@ -127,6 +145,14 @@ class TxData {
|
|||
TransactionSubType? txSubType,
|
||||
List<Map<String, dynamic>>? mintsMapLelantus,
|
||||
tezart.OperationsList? tezosOperationsList,
|
||||
List<
|
||||
({
|
||||
String address,
|
||||
Amount amount,
|
||||
String memo,
|
||||
})>?
|
||||
sparkRecipients,
|
||||
List<TxData>? sparkMints,
|
||||
}) {
|
||||
return TxData(
|
||||
feeRateType: feeRateType ?? this.feeRateType,
|
||||
|
@ -159,6 +185,8 @@ class TxData {
|
|||
txSubType: txSubType ?? this.txSubType,
|
||||
mintsMapLelantus: mintsMapLelantus ?? this.mintsMapLelantus,
|
||||
tezosOperationsList: tezosOperationsList ?? this.tezosOperationsList,
|
||||
sparkRecipients: sparkRecipients ?? this.sparkRecipients,
|
||||
sparkMints: sparkMints ?? this.sparkMints,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -193,5 +221,7 @@ class TxData {
|
|||
'txSubType: $txSubType, '
|
||||
'mintsMapLelantus: $mintsMapLelantus, '
|
||||
'tezosOperationsList: $tezosOperationsList, '
|
||||
'sparkRecipients: $sparkRecipients, '
|
||||
'sparkMints: $sparkMints, '
|
||||
'}';
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class BitcoinWallet extends Bip39HDWallet
|
|||
// ===========================================================================
|
||||
|
||||
@override
|
||||
Future<List<Address>> fetchAllOwnAddresses() async {
|
||||
Future<List<Address>> fetchAddressesForElectrumXScan() async {
|
||||
final allAddresses = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
|
@ -51,7 +51,7 @@ class BitcoinWallet extends Bip39HDWallet
|
|||
|
||||
// TODO: [prio=med] switch to V2 transactions
|
||||
final data = await fetchTransactionsV1(
|
||||
addresses: await fetchAllOwnAddresses(),
|
||||
addresses: await fetchAddressesForElectrumXScan(),
|
||||
currentChainHeight: currentChainHeight,
|
||||
);
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ class BitcoincashWallet extends Bip39HDWallet
|
|||
// ===========================================================================
|
||||
|
||||
@override
|
||||
Future<List<Address>> fetchAllOwnAddresses() async {
|
||||
Future<List<Address>> fetchAddressesForElectrumXScan() async {
|
||||
final allAddresses = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
|
@ -94,7 +94,7 @@ class BitcoincashWallet extends Bip39HDWallet
|
|||
|
||||
@override
|
||||
Future<void> updateTransactions() async {
|
||||
List<Address> allAddressesOld = await fetchAllOwnAddresses();
|
||||
List<Address> allAddressesOld = await fetchAddressesForElectrumXScan();
|
||||
|
||||
Set<String> receivingAddresses = allAddressesOld
|
||||
.where((e) => e.subType == AddressSubType.receiving)
|
||||
|
@ -293,6 +293,7 @@ class BitcoincashWallet extends Bip39HDWallet
|
|||
outputs: List.unmodifiable(outputs),
|
||||
type: type,
|
||||
subType: subType,
|
||||
otherData: null,
|
||||
);
|
||||
|
||||
txns.add(tx);
|
||||
|
|
|
@ -24,7 +24,7 @@ class DogecoinWallet extends Bip39HDWallet
|
|||
// ===========================================================================
|
||||
|
||||
@override
|
||||
Future<List<Address>> fetchAllOwnAddresses() async {
|
||||
Future<List<Address>> fetchAddressesForElectrumXScan() async {
|
||||
final allAddresses = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
|
@ -47,7 +47,7 @@ class DogecoinWallet extends Bip39HDWallet
|
|||
|
||||
// TODO: [prio=med] switch to V2 transactions
|
||||
final data = await fetchTransactionsV1(
|
||||
addresses: await fetchAllOwnAddresses(),
|
||||
addresses: await fetchAddressesForElectrumXScan(),
|
||||
currentChainHeight: currentChainHeight,
|
||||
);
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ class EcashWallet extends Bip39HDWallet
|
|||
// ===========================================================================
|
||||
|
||||
@override
|
||||
Future<List<Address>> fetchAllOwnAddresses() async {
|
||||
Future<List<Address>> fetchAddressesForElectrumXScan() async {
|
||||
final allAddresses = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
|
@ -87,7 +87,7 @@ class EcashWallet extends Bip39HDWallet
|
|||
|
||||
@override
|
||||
Future<void> updateTransactions() async {
|
||||
List<Address> allAddressesOld = await fetchAllOwnAddresses();
|
||||
List<Address> allAddressesOld = await fetchAddressesForElectrumXScan();
|
||||
|
||||
Set<String> receivingAddresses = allAddressesOld
|
||||
.where((e) => e.subType == AddressSubType.receiving)
|
||||
|
@ -169,7 +169,7 @@ class EcashWallet extends Bip39HDWallet
|
|||
final prevOut = OutputV2.fromElectrumXJson(
|
||||
prevOutJson,
|
||||
decimalPlaces: cryptoCurrency.fractionDigits,
|
||||
isECashFullAmountNotSats: true,
|
||||
isFullAmountNotSats: true,
|
||||
walletOwns: false, // doesn't matter here as this is not saved
|
||||
);
|
||||
|
||||
|
@ -208,7 +208,7 @@ class EcashWallet extends Bip39HDWallet
|
|||
OutputV2 output = OutputV2.fromElectrumXJson(
|
||||
Map<String, dynamic>.from(outputJson as Map),
|
||||
decimalPlaces: cryptoCurrency.fractionDigits,
|
||||
isECashFullAmountNotSats: true,
|
||||
isFullAmountNotSats: true,
|
||||
// don't know yet if wallet owns. Need addresses first
|
||||
walletOwns: false,
|
||||
);
|
||||
|
@ -288,6 +288,7 @@ class EcashWallet extends Bip39HDWallet
|
|||
outputs: List.unmodifiable(outputs),
|
||||
type: type,
|
||||
subType: subType,
|
||||
otherData: null,
|
||||
);
|
||||
|
||||
txns.add(tx);
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:stackwallet/db/hive/db.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/input.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/output.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||
import 'package:stackwallet/models/isar/models/firo_specific/lelantus_coin.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||
import 'package:stackwallet/utilities/extensions/extensions.dart';
|
||||
import 'package:stackwallet/utilities/logger.dart';
|
||||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||
import 'package:stackwallet/wallets/isar/models/spark_coin.dart';
|
||||
import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
const sparkStartBlock = 819300; // (approx 18 Jan 2024)
|
||||
|
||||
|
@ -27,6 +30,9 @@ class FiroWallet extends Bip39HDWallet
|
|||
|
||||
FiroWallet(CryptoCurrencyNetwork network) : super(Firo(network));
|
||||
|
||||
@override
|
||||
int get isarTransactionVersion => 2;
|
||||
|
||||
@override
|
||||
FilterOperation? get changeAddressFilterOperation =>
|
||||
FilterGroup.and(standardChangeAddressFilters);
|
||||
|
@ -37,49 +43,43 @@ class FiroWallet extends Bip39HDWallet
|
|||
|
||||
// ===========================================================================
|
||||
|
||||
@override
|
||||
Future<List<Address>> fetchAllOwnAddresses() async {
|
||||
final allAddresses = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.not()
|
||||
.group(
|
||||
(q) => q
|
||||
.typeEqualTo(AddressType.nonWallet)
|
||||
.or()
|
||||
.subTypeEqualTo(AddressSubType.nonWallet),
|
||||
)
|
||||
.findAll();
|
||||
return allAddresses;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
|
||||
bool _duplicateTxCheck(
|
||||
List<Map<String, dynamic>> allTransactions, String txid) {
|
||||
for (int i = 0; i < allTransactions.length; i++) {
|
||||
if (allTransactions[i]["txid"] == txid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateTransactions() async {
|
||||
final allAddresses = await fetchAllOwnAddresses();
|
||||
List<Address> allAddressesOld = await fetchAddressesForElectrumXScan();
|
||||
|
||||
Set<String> receivingAddresses = allAddresses
|
||||
Set<String> receivingAddresses = allAddressesOld
|
||||
.where((e) => e.subType == AddressSubType.receiving)
|
||||
.map((e) => e.value)
|
||||
.map((e) => convertAddressString(e.value))
|
||||
.toSet();
|
||||
Set<String> changeAddresses = allAddresses
|
||||
|
||||
Set<String> changeAddresses = allAddressesOld
|
||||
.where((e) => e.subType == AddressSubType.change)
|
||||
.map((e) => e.value)
|
||||
.map((e) => convertAddressString(e.value))
|
||||
.toSet();
|
||||
|
||||
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
|
||||
|
||||
final List<Map<String, dynamic>> allTxHashes =
|
||||
await fetchHistory(allAddresses.map((e) => e.value).toList());
|
||||
await fetchHistory(allAddressesSet);
|
||||
|
||||
final sparkCoins = await mainDB.isar.sparkCoins
|
||||
.where()
|
||||
.walletIdEqualToAnyLTagHash(walletId)
|
||||
.findAll();
|
||||
|
||||
final Set<String> sparkTxids = {};
|
||||
|
||||
for (final coin in sparkCoins) {
|
||||
sparkTxids.add(coin.txHash);
|
||||
// check for duplicates before adding to list
|
||||
if (allTxHashes.indexWhere((e) => e["tx_hash"] == coin.txHash) == -1) {
|
||||
final info = {
|
||||
"tx_hash": coin.txHash,
|
||||
"height": coin.height,
|
||||
};
|
||||
allTxHashes.add(info);
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> allTransactions = [];
|
||||
|
||||
|
@ -110,8 +110,6 @@ class FiroWallet extends Bip39HDWallet
|
|||
}
|
||||
}
|
||||
|
||||
// final currentHeight = await chainHeight;
|
||||
|
||||
for (final txHash in allTxHashes) {
|
||||
// final storedTx = await db
|
||||
// .getTransactions(walletId)
|
||||
|
@ -127,508 +125,372 @@ class FiroWallet extends Bip39HDWallet
|
|||
coin: info.coin,
|
||||
);
|
||||
|
||||
if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) {
|
||||
tx["address"] = await mainDB
|
||||
.getAddresses(walletId)
|
||||
.filter()
|
||||
.valueEqualTo(txHash["address"] as String)
|
||||
.findFirst();
|
||||
tx["height"] = txHash["height"];
|
||||
// check for duplicates before adding to list
|
||||
if (allTransactions
|
||||
.indexWhere((e) => e["txid"] == tx["txid"] as String) ==
|
||||
-1) {
|
||||
tx["height"] ??= txHash["height"];
|
||||
allTransactions.add(tx);
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
final List<Tuple2<Transaction, Address?>> txnsData = [];
|
||||
final List<TransactionV2> txns = [];
|
||||
|
||||
for (final txObject in allTransactions) {
|
||||
final inputList = txObject["vin"] as List;
|
||||
final outputList = txObject["vout"] as List;
|
||||
for (final txData in allTransactions) {
|
||||
// set to true if any inputs were detected as owned by this wallet
|
||||
bool wasSentFromThisWallet = false;
|
||||
|
||||
// set to true if any outputs were detected as owned by this wallet
|
||||
bool wasReceivedInThisWallet = false;
|
||||
BigInt amountReceivedInThisWallet = BigInt.zero;
|
||||
BigInt changeAmountReceivedInThisWallet = BigInt.zero;
|
||||
|
||||
Amount? anonFees;
|
||||
|
||||
bool isMint = false;
|
||||
bool isJMint = false;
|
||||
bool isSparkMint = false;
|
||||
bool isMasterNodePayment = false;
|
||||
final bool isSparkSpend = txData["type"] == 9 && txData["version"] == 3;
|
||||
final bool isMySpark = sparkTxids.contains(txData["txid"] as String);
|
||||
|
||||
// check if tx is Mint or jMint
|
||||
for (final output in outputList) {
|
||||
if (output["scriptPubKey"]?["type"] == "lelantusmint") {
|
||||
final asm = output["scriptPubKey"]?["asm"] as String?;
|
||||
final sparkCoinsInvolved =
|
||||
sparkCoins.where((e) => e.txHash == txData["txid"]);
|
||||
if (isMySpark && sparkCoinsInvolved.isEmpty) {
|
||||
Logging.instance.log(
|
||||
"sparkCoinsInvolved is empty and should not be! (ignoring tx parsing)",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// parse outputs
|
||||
final List<OutputV2> outputs = [];
|
||||
for (final outputJson in txData["vout"] as List) {
|
||||
final outMap = Map<String, dynamic>.from(outputJson as Map);
|
||||
if (outMap["scriptPubKey"]?["type"] == "lelantusmint") {
|
||||
final asm = outMap["scriptPubKey"]?["asm"] as String?;
|
||||
if (asm != null) {
|
||||
if (asm.startsWith("OP_LELANTUSJMINT")) {
|
||||
isJMint = true;
|
||||
break;
|
||||
} else if (asm.startsWith("OP_LELANTUSMINT")) {
|
||||
isMint = true;
|
||||
break;
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
"Unknown mint op code found for lelantusmint tx: ${txObject["txid"]}",
|
||||
"Unknown mint op code found for lelantusmint tx: ${txData["txid"]}",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
"ASM for lelantusmint tx: ${txObject["txid"]} is null!",
|
||||
"ASM for lelantusmint tx: ${txData["txid"]} is null!",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (outMap["scriptPubKey"]?["type"] == "sparkmint" ||
|
||||
outMap["scriptPubKey"]?["type"] == "sparksmint") {
|
||||
final asm = outMap["scriptPubKey"]?["asm"] as String?;
|
||||
if (asm != null) {
|
||||
if (asm.startsWith("OP_SPARKMINT") ||
|
||||
asm.startsWith("OP_SPARKSMINT")) {
|
||||
isSparkMint = true;
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
"Unknown mint op code found for sparkmint tx: ${txData["txid"]}",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
"ASM for sparkmint tx: ${txData["txid"]} is null!",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> inputAddresses = {};
|
||||
Set<String> outputAddresses = {};
|
||||
|
||||
Amount totalInputValue = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
Amount totalOutputValue = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
OutputV2 output = OutputV2.fromElectrumXJson(
|
||||
outMap,
|
||||
decimalPlaces: cryptoCurrency.fractionDigits,
|
||||
isFullAmountNotSats: true,
|
||||
// don't know yet if wallet owns. Need addresses first
|
||||
walletOwns: false,
|
||||
);
|
||||
|
||||
Amount amountSentFromWallet = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
Amount amountReceivedInWallet = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
Amount changeAmount = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
// if (isSparkSpend) {
|
||||
// // TODO?
|
||||
// } else
|
||||
if (isSparkMint) {
|
||||
if (isMySpark) {
|
||||
if (output.addresses.isEmpty &&
|
||||
output.scriptPubKeyHex.length >= 488) {
|
||||
// likely spark related
|
||||
final opByte = output.scriptPubKeyHex
|
||||
.substring(0, 2)
|
||||
.toUint8ListFromHex
|
||||
.first;
|
||||
if (opByte == OP_SPARKMINT || opByte == OP_SPARKSMINT) {
|
||||
final serCoin = base64Encode(output.scriptPubKeyHex
|
||||
.substring(2, 488)
|
||||
.toUint8ListFromHex);
|
||||
final coin = sparkCoinsInvolved
|
||||
.where((e) => e.serializedCoinB64!.startsWith(serCoin))
|
||||
.firstOrNull;
|
||||
|
||||
// Parse mint transaction ================================================
|
||||
// We should be able to assume this belongs to this wallet
|
||||
if (isMint) {
|
||||
List<Input> ins = [];
|
||||
if (coin == null) {
|
||||
// not ours
|
||||
} else {
|
||||
output = output.copyWith(
|
||||
walletOwns: true,
|
||||
valueStringSats: coin.value.toString(),
|
||||
addresses: [
|
||||
coin.address,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (isMint || isJMint) {
|
||||
// do nothing extra ?
|
||||
} else {
|
||||
// TODO?
|
||||
}
|
||||
|
||||
// Parse inputs
|
||||
for (final input in inputList) {
|
||||
// Both value and address should not be null for a mint
|
||||
final address = input["address"] as String?;
|
||||
final value = input["valueSat"] as int?;
|
||||
// if output was to my wallet, add value to amount received
|
||||
if (receivingAddresses
|
||||
.intersection(output.addresses.toSet())
|
||||
.isNotEmpty) {
|
||||
wasReceivedInThisWallet = true;
|
||||
amountReceivedInThisWallet += output.value;
|
||||
output = output.copyWith(walletOwns: true);
|
||||
} else if (changeAddresses
|
||||
.intersection(output.addresses.toSet())
|
||||
.isNotEmpty) {
|
||||
wasReceivedInThisWallet = true;
|
||||
changeAmountReceivedInThisWallet += output.value;
|
||||
output = output.copyWith(walletOwns: true);
|
||||
} else if (isSparkMint && isMySpark) {
|
||||
wasReceivedInThisWallet = true;
|
||||
if (output.addresses.contains(sparkChangeAddress)) {
|
||||
changeAmountReceivedInThisWallet += output.value;
|
||||
} else {
|
||||
amountReceivedInThisWallet += output.value;
|
||||
}
|
||||
}
|
||||
|
||||
// We should not need to check whether the mint belongs to this
|
||||
// wallet as any tx we look up will be looked up by one of this
|
||||
// wallet's addresses
|
||||
if (address != null && value != null) {
|
||||
totalInputValue += value.toAmountAsRaw(
|
||||
outputs.add(output);
|
||||
}
|
||||
|
||||
if (isJMint || isSparkSpend) {
|
||||
anonFees = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
}
|
||||
|
||||
ins.add(
|
||||
Input(
|
||||
txid: input['txid'] as String? ?? "",
|
||||
vout: input['vout'] as int? ?? -1,
|
||||
scriptSig: input['scriptSig']?['hex'] as String?,
|
||||
scriptSigAsm: input['scriptSig']?['asm'] as String?,
|
||||
isCoinbase: input['is_coinbase'] as bool?,
|
||||
sequence: input['sequence'] as int?,
|
||||
innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?,
|
||||
),
|
||||
// parse inputs
|
||||
final List<InputV2> inputs = [];
|
||||
for (final jsonInput in txData["vin"] as List) {
|
||||
final map = Map<String, dynamic>.from(jsonInput as Map);
|
||||
|
||||
final List<String> addresses = [];
|
||||
String valueStringSats = "0";
|
||||
OutpointV2? outpoint;
|
||||
|
||||
final coinbase = map["coinbase"] as String?;
|
||||
|
||||
final txid = map["txid"] as String?;
|
||||
final vout = map["vout"] as int?;
|
||||
if (txid != null && vout != null) {
|
||||
outpoint = OutpointV2.isarCantDoRequiredInDefaultConstructor(
|
||||
txid: txid,
|
||||
vout: vout,
|
||||
);
|
||||
}
|
||||
|
||||
// Parse outputs
|
||||
for (final output in outputList) {
|
||||
// get value
|
||||
final value = Amount.fromDecimal(
|
||||
Decimal.parse(output["value"].toString()),
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
|
||||
// add value to total
|
||||
totalOutputValue += value;
|
||||
}
|
||||
|
||||
final fee = totalInputValue - totalOutputValue;
|
||||
final tx = Transaction(
|
||||
walletId: walletId,
|
||||
txid: txObject["txid"] as String,
|
||||
timestamp: txObject["blocktime"] as int? ??
|
||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||
type: TransactionType.sentToSelf,
|
||||
subType: TransactionSubType.mint,
|
||||
amount: totalOutputValue.raw.toInt(),
|
||||
amountString: totalOutputValue.toJsonString(),
|
||||
fee: fee.raw.toInt(),
|
||||
height: txObject["height"] as int?,
|
||||
isCancelled: false,
|
||||
isLelantus: true,
|
||||
slateId: null,
|
||||
otherData: null,
|
||||
nonce: null,
|
||||
inputs: ins,
|
||||
outputs: [],
|
||||
numberOfMessages: null,
|
||||
);
|
||||
|
||||
txnsData.add(Tuple2(tx, null));
|
||||
|
||||
// Otherwise parse JMint transaction ===================================
|
||||
} else if (isJMint) {
|
||||
Amount jMintFees = Amount(
|
||||
rawValue: BigInt.zero,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
|
||||
// Parse inputs
|
||||
List<Input> ins = [];
|
||||
for (final input in inputList) {
|
||||
// JMint fee
|
||||
final nFee = Decimal.tryParse(input["nFees"].toString());
|
||||
if (isSparkSpend) {
|
||||
// anon fees
|
||||
final nFee = Decimal.tryParse(map["nFees"].toString());
|
||||
if (nFee != null) {
|
||||
final fees = Amount.fromDecimal(
|
||||
nFee,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
|
||||
jMintFees += fees;
|
||||
anonFees = anonFees! + fees;
|
||||
}
|
||||
} else if (isSparkMint) {
|
||||
final address = map["address"] as String?;
|
||||
final value = map["valueSat"] as int?;
|
||||
|
||||
ins.add(
|
||||
Input(
|
||||
txid: input['txid'] as String? ?? "",
|
||||
vout: input['vout'] as int? ?? -1,
|
||||
scriptSig: input['scriptSig']?['hex'] as String?,
|
||||
scriptSigAsm: input['scriptSig']?['asm'] as String?,
|
||||
isCoinbase: input['is_coinbase'] as bool?,
|
||||
sequence: input['sequence'] as int?,
|
||||
innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?,
|
||||
),
|
||||
);
|
||||
if (address != null && value != null) {
|
||||
valueStringSats = value.toString();
|
||||
addresses.add(address);
|
||||
}
|
||||
} else if (isMint) {
|
||||
// We should be able to assume this belongs to this wallet
|
||||
final address = map["address"] as String?;
|
||||
final value = map["valueSat"] as int?;
|
||||
|
||||
bool nonWalletAddressFoundInOutputs = false;
|
||||
|
||||
// Parse outputs
|
||||
List<Output> outs = [];
|
||||
for (final output in outputList) {
|
||||
// get value
|
||||
final value = Amount.fromDecimal(
|
||||
Decimal.parse(output["value"].toString()),
|
||||
if (address != null && value != null) {
|
||||
valueStringSats = value.toString();
|
||||
addresses.add(address);
|
||||
}
|
||||
} else if (isJMint) {
|
||||
// anon fees
|
||||
final nFee = Decimal.tryParse(map["nFees"].toString());
|
||||
if (nFee != null) {
|
||||
final fees = Amount.fromDecimal(
|
||||
nFee,
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
|
||||
// add value to total
|
||||
totalOutputValue += value;
|
||||
|
||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output['scriptPubKey']?['address'] as String?;
|
||||
|
||||
if (address != null) {
|
||||
outputAddresses.add(address);
|
||||
if (receivingAddresses.contains(address) ||
|
||||
changeAddresses.contains(address)) {
|
||||
amountReceivedInWallet += value;
|
||||
} else {
|
||||
nonWalletAddressFoundInOutputs = true;
|
||||
anonFees = anonFees! + fees;
|
||||
}
|
||||
}
|
||||
|
||||
outs.add(
|
||||
Output(
|
||||
scriptPubKey: output['scriptPubKey']?['hex'] as String?,
|
||||
scriptPubKeyAsm: output['scriptPubKey']?['asm'] as String?,
|
||||
scriptPubKeyType: output['scriptPubKey']?['type'] as String?,
|
||||
scriptPubKeyAddress: address ?? "jmint",
|
||||
value: value.raw.toInt(),
|
||||
),
|
||||
);
|
||||
}
|
||||
final txid = txObject["txid"] as String;
|
||||
|
||||
const subType = TransactionSubType.join;
|
||||
|
||||
final type = nonWalletAddressFoundInOutputs
|
||||
? TransactionType.outgoing
|
||||
: (await mainDB.isar.lelantusCoins
|
||||
.where()
|
||||
.walletIdEqualTo(walletId)
|
||||
.filter()
|
||||
.txidEqualTo(txid)
|
||||
.findFirst()) ==
|
||||
null
|
||||
? TransactionType.incoming
|
||||
: TransactionType.sentToSelf;
|
||||
|
||||
final amount = nonWalletAddressFoundInOutputs
|
||||
? totalOutputValue
|
||||
: amountReceivedInWallet;
|
||||
|
||||
final possibleNonWalletAddresses =
|
||||
receivingAddresses.difference(outputAddresses);
|
||||
final possibleReceivingAddresses =
|
||||
receivingAddresses.intersection(outputAddresses);
|
||||
|
||||
final transactionAddress = nonWalletAddressFoundInOutputs
|
||||
? Address(
|
||||
walletId: walletId,
|
||||
value: possibleNonWalletAddresses.first,
|
||||
derivationIndex: -1,
|
||||
derivationPath: null,
|
||||
type: AddressType.nonWallet,
|
||||
subType: AddressSubType.nonWallet,
|
||||
publicKey: [],
|
||||
)
|
||||
: allAddresses.firstWhere(
|
||||
(e) => e.value == possibleReceivingAddresses.first,
|
||||
} else if (coinbase == null && txid != null && vout != null) {
|
||||
final inputTx = await electrumXCachedClient.getTransaction(
|
||||
txHash: txid,
|
||||
coin: cryptoCurrency.coin,
|
||||
);
|
||||
|
||||
final tx = Transaction(
|
||||
walletId: walletId,
|
||||
txid: txid,
|
||||
timestamp: txObject["blocktime"] as int? ??
|
||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||
type: type,
|
||||
subType: subType,
|
||||
amount: amount.raw.toInt(),
|
||||
amountString: amount.toJsonString(),
|
||||
fee: jMintFees.raw.toInt(),
|
||||
height: txObject["height"] as int?,
|
||||
isCancelled: false,
|
||||
isLelantus: true,
|
||||
slateId: null,
|
||||
otherData: null,
|
||||
nonce: null,
|
||||
inputs: ins,
|
||||
outputs: outs,
|
||||
numberOfMessages: null,
|
||||
final prevOutJson = Map<String, dynamic>.from(
|
||||
(inputTx["vout"] as List).firstWhere((e) => e["n"] == vout)
|
||||
as Map);
|
||||
|
||||
final prevOut = OutputV2.fromElectrumXJson(
|
||||
prevOutJson,
|
||||
decimalPlaces: cryptoCurrency.fractionDigits,
|
||||
isFullAmountNotSats: true,
|
||||
walletOwns: false, // doesn't matter here as this is not saved
|
||||
);
|
||||
|
||||
txnsData.add(Tuple2(tx, transactionAddress));
|
||||
valueStringSats = prevOut.valueStringSats;
|
||||
addresses.addAll(prevOut.addresses);
|
||||
} else if (coinbase == null) {
|
||||
Util.printJson(map, "NON TXID INPUT");
|
||||
}
|
||||
|
||||
// Master node payment =====================================
|
||||
} else if (inputList.length == 1 &&
|
||||
inputList.first["coinbase"] is String) {
|
||||
List<Input> ins = [
|
||||
Input(
|
||||
txid: inputList.first["coinbase"] as String,
|
||||
vout: -1,
|
||||
scriptSig: null,
|
||||
scriptSigAsm: null,
|
||||
isCoinbase: true,
|
||||
sequence: inputList.first['sequence'] as int?,
|
||||
innerRedeemScriptAsm: null,
|
||||
),
|
||||
];
|
||||
|
||||
// parse outputs
|
||||
List<Output> outs = [];
|
||||
for (final output in outputList) {
|
||||
// get value
|
||||
final value = Amount.fromDecimal(
|
||||
Decimal.parse(output["value"].toString()),
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor(
|
||||
scriptSigHex: map["scriptSig"]?["hex"] as String?,
|
||||
sequence: map["sequence"] as int?,
|
||||
outpoint: outpoint,
|
||||
valueStringSats: valueStringSats,
|
||||
addresses: addresses,
|
||||
witness: map["witness"] as String?,
|
||||
coinbase: coinbase,
|
||||
innerRedeemScriptAsm: map["innerRedeemscriptAsm"] as String?,
|
||||
// don't know yet if wallet owns. Need addresses first
|
||||
walletOwns: false,
|
||||
);
|
||||
|
||||
// get output address
|
||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
if (address != null) {
|
||||
outputAddresses.add(address);
|
||||
if (allAddressesSet.intersection(input.addresses.toSet()).isNotEmpty) {
|
||||
wasSentFromThisWallet = true;
|
||||
input = input.copyWith(walletOwns: true);
|
||||
} else if (isMySpark) {
|
||||
final lTags = map["lTags"] as List?;
|
||||
|
||||
// if output was to my wallet, add value to amount received
|
||||
if (receivingAddresses.contains(address)) {
|
||||
amountReceivedInWallet += value;
|
||||
}
|
||||
if (lTags?.isNotEmpty == true) {
|
||||
final List<SparkCoin> usedCoins = [];
|
||||
for (final tag in lTags!) {
|
||||
final components = (tag as String).split(",");
|
||||
final x = components[0].substring(1);
|
||||
final y = components[1].substring(0, components[1].length - 1);
|
||||
|
||||
final hash = LibSpark.hashTag(x, y);
|
||||
usedCoins.addAll(sparkCoins.where((e) => e.lTagHash == hash));
|
||||
}
|
||||
|
||||
outs.add(
|
||||
Output(
|
||||
scriptPubKey: output['scriptPubKey']?['hex'] as String?,
|
||||
scriptPubKeyAsm: output['scriptPubKey']?['asm'] as String?,
|
||||
scriptPubKeyType: output['scriptPubKey']?['type'] as String?,
|
||||
scriptPubKeyAddress: address ?? "",
|
||||
value: value.raw.toInt(),
|
||||
),
|
||||
if (usedCoins.isNotEmpty) {
|
||||
input = input.copyWith(
|
||||
addresses: usedCoins.map((e) => e.address).toList(),
|
||||
valueStringSats: usedCoins
|
||||
.map((e) => e.value)
|
||||
.reduce((value, element) => value += element)
|
||||
.toString(),
|
||||
walletOwns: true,
|
||||
);
|
||||
wasSentFromThisWallet = true;
|
||||
}
|
||||
|
||||
// this is the address initially used to fetch the txid
|
||||
Address transactionAddress = txObject["address"] as Address;
|
||||
|
||||
final tx = Transaction(
|
||||
walletId: walletId,
|
||||
txid: txObject["txid"] as String,
|
||||
timestamp: txObject["blocktime"] as int? ??
|
||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||
type: TransactionType.incoming,
|
||||
subType: TransactionSubType.none,
|
||||
// amount may overflow. Deprecated. Use amountString
|
||||
amount: amountReceivedInWallet.raw.toInt(),
|
||||
amountString: amountReceivedInWallet.toJsonString(),
|
||||
fee: 0,
|
||||
height: txObject["height"] as int?,
|
||||
isCancelled: false,
|
||||
isLelantus: false,
|
||||
slateId: null,
|
||||
otherData: null,
|
||||
nonce: null,
|
||||
inputs: ins,
|
||||
outputs: outs,
|
||||
numberOfMessages: null,
|
||||
);
|
||||
|
||||
txnsData.add(Tuple2(tx, transactionAddress));
|
||||
|
||||
// Assume non lelantus transaction =====================================
|
||||
} else {
|
||||
// parse inputs
|
||||
List<Input> ins = [];
|
||||
for (final input in inputList) {
|
||||
final valueSat = input["valueSat"] as int?;
|
||||
final address = input["address"] as String? ??
|
||||
input["scriptPubKey"]?["address"] as String? ??
|
||||
input["scriptPubKey"]?["addresses"]?[0] as String?;
|
||||
|
||||
if (address != null && valueSat != null) {
|
||||
final value = valueSat.toAmountAsRaw(
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
|
||||
// add value to total
|
||||
totalInputValue += value;
|
||||
inputAddresses.add(address);
|
||||
|
||||
// if input was from my wallet, add value to amount sent
|
||||
if (receivingAddresses.contains(address) ||
|
||||
changeAddresses.contains(address)) {
|
||||
amountSentFromWallet += value;
|
||||
}
|
||||
}
|
||||
|
||||
ins.add(
|
||||
Input(
|
||||
txid: input['txid'] as String,
|
||||
vout: input['vout'] as int? ?? -1,
|
||||
scriptSig: input['scriptSig']?['hex'] as String?,
|
||||
scriptSigAsm: input['scriptSig']?['asm'] as String?,
|
||||
isCoinbase: input['is_coinbase'] as bool?,
|
||||
sequence: input['sequence'] as int?,
|
||||
innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?,
|
||||
),
|
||||
);
|
||||
inputs.add(input);
|
||||
}
|
||||
|
||||
// parse outputs
|
||||
List<Output> outs = [];
|
||||
for (final output in outputList) {
|
||||
// get value
|
||||
final value = Amount.fromDecimal(
|
||||
Decimal.parse(output["value"].toString()),
|
||||
fractionDigits: cryptoCurrency.fractionDigits,
|
||||
);
|
||||
|
||||
// add value to total
|
||||
totalOutputValue += value;
|
||||
|
||||
// get output address
|
||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||
output["scriptPubKey"]?["address"] as String?;
|
||||
if (address != null) {
|
||||
outputAddresses.add(address);
|
||||
|
||||
// if output was to my wallet, add value to amount received
|
||||
if (receivingAddresses.contains(address)) {
|
||||
amountReceivedInWallet += value;
|
||||
} else if (changeAddresses.contains(address)) {
|
||||
changeAmount += value;
|
||||
}
|
||||
}
|
||||
|
||||
outs.add(
|
||||
Output(
|
||||
scriptPubKey: output['scriptPubKey']?['hex'] as String?,
|
||||
scriptPubKeyAsm: output['scriptPubKey']?['asm'] as String?,
|
||||
scriptPubKeyType: output['scriptPubKey']?['type'] as String?,
|
||||
scriptPubKeyAddress: address ?? "",
|
||||
value: value.raw.toInt(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final mySentFromAddresses = [
|
||||
...receivingAddresses.intersection(inputAddresses),
|
||||
...changeAddresses.intersection(inputAddresses)
|
||||
];
|
||||
final myReceivedOnAddresses =
|
||||
receivingAddresses.intersection(outputAddresses);
|
||||
final myChangeReceivedOnAddresses =
|
||||
changeAddresses.intersection(outputAddresses);
|
||||
|
||||
final fee = totalInputValue - totalOutputValue;
|
||||
|
||||
// this is the address initially used to fetch the txid
|
||||
Address transactionAddress = txObject["address"] as Address;
|
||||
final totalOut = outputs
|
||||
.map((e) => e.value)
|
||||
.fold(BigInt.zero, (value, element) => value + element);
|
||||
|
||||
TransactionType type;
|
||||
Amount amount;
|
||||
if (mySentFromAddresses.isNotEmpty &&
|
||||
myReceivedOnAddresses.isNotEmpty) {
|
||||
// tx is sent to self
|
||||
type = TransactionType.sentToSelf;
|
||||
TransactionSubType subType = TransactionSubType.none;
|
||||
|
||||
// should be 0
|
||||
amount = amountSentFromWallet -
|
||||
amountReceivedInWallet -
|
||||
fee -
|
||||
changeAmount;
|
||||
} else if (mySentFromAddresses.isNotEmpty) {
|
||||
// outgoing tx
|
||||
// TODO integrate the following with the next bit
|
||||
if (isSparkSpend) {
|
||||
subType = TransactionSubType.sparkSpend;
|
||||
} else if (isSparkMint) {
|
||||
subType = TransactionSubType.sparkMint;
|
||||
} else if (isMint) {
|
||||
subType = TransactionSubType.mint;
|
||||
} else if (isJMint) {
|
||||
subType = TransactionSubType.join;
|
||||
}
|
||||
|
||||
// at least one input was owned by this wallet
|
||||
if (wasSentFromThisWallet) {
|
||||
type = TransactionType.outgoing;
|
||||
amount = amountSentFromWallet - changeAmount - fee;
|
||||
|
||||
final possible =
|
||||
outputAddresses.difference(myChangeReceivedOnAddresses).first;
|
||||
|
||||
if (transactionAddress.value != possible) {
|
||||
transactionAddress = Address(
|
||||
walletId: walletId,
|
||||
value: possible,
|
||||
derivationIndex: -1,
|
||||
derivationPath: null,
|
||||
subType: AddressSubType.nonWallet,
|
||||
type: AddressType.nonWallet,
|
||||
publicKey: [],
|
||||
);
|
||||
if (wasReceivedInThisWallet) {
|
||||
if (changeAmountReceivedInThisWallet + amountReceivedInThisWallet ==
|
||||
totalOut) {
|
||||
// definitely sent all to self
|
||||
type = TransactionType.sentToSelf;
|
||||
} else if (amountReceivedInThisWallet == BigInt.zero) {
|
||||
// most likely just a typical send
|
||||
// do nothing here yet
|
||||
}
|
||||
} else {
|
||||
// incoming tx
|
||||
}
|
||||
} else if (wasReceivedInThisWallet) {
|
||||
// only found outputs owned by this wallet
|
||||
type = TransactionType.incoming;
|
||||
amount = amountReceivedInWallet;
|
||||
} else {
|
||||
Logging.instance.log(
|
||||
"Unexpected tx found (ignoring it): $txData",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
final tx = Transaction(
|
||||
String? otherData;
|
||||
if (anonFees != null) {
|
||||
otherData = jsonEncode(
|
||||
{
|
||||
"anonFees": anonFees.toJsonString(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final tx = TransactionV2(
|
||||
walletId: walletId,
|
||||
txid: txObject["txid"] as String,
|
||||
timestamp: txObject["blocktime"] as int? ??
|
||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
||||
blockHash: txData["blockhash"] as String?,
|
||||
hash: txData["hash"] as String,
|
||||
txid: txData["txid"] as String,
|
||||
height: txData["height"] as int?,
|
||||
version: txData["version"] as int,
|
||||
timestamp: txData["blocktime"] as int? ??
|
||||
DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
|
||||
inputs: List.unmodifiable(inputs),
|
||||
outputs: List.unmodifiable(outputs),
|
||||
type: type,
|
||||
subType: TransactionSubType.none,
|
||||
// amount may overflow. Deprecated. Use amountString
|
||||
amount: amount.raw.toInt(),
|
||||
amountString: amount.toJsonString(),
|
||||
fee: fee.raw.toInt(),
|
||||
height: txObject["height"] as int?,
|
||||
isCancelled: false,
|
||||
isLelantus: false,
|
||||
slateId: null,
|
||||
otherData: null,
|
||||
nonce: null,
|
||||
inputs: ins,
|
||||
outputs: outs,
|
||||
numberOfMessages: null,
|
||||
subType: subType,
|
||||
otherData: otherData,
|
||||
);
|
||||
|
||||
txnsData.add(Tuple2(tx, transactionAddress));
|
||||
}
|
||||
txns.add(tx);
|
||||
}
|
||||
|
||||
await mainDB.addNewTransactionData(txnsData, walletId);
|
||||
await mainDB.updateOrPutTransactionV2s(txns);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -689,6 +551,7 @@ class FiroWallet extends Bip39HDWallet
|
|||
await mainDB.deleteWalletBlockchainData(walletId);
|
||||
}
|
||||
|
||||
// lelantus
|
||||
final latestSetId = await electrumXClient.getLelantusLatestCoinId();
|
||||
final setDataMapFuture = getSetDataMap(latestSetId);
|
||||
final usedSerialNumbersFuture =
|
||||
|
@ -696,6 +559,17 @@ class FiroWallet extends Bip39HDWallet
|
|||
coin: info.coin,
|
||||
);
|
||||
|
||||
// spark
|
||||
final latestSparkCoinId = await electrumXClient.getSparkLatestCoinId();
|
||||
final sparkAnonSetFuture = electrumXCachedClient.getSparkAnonymitySet(
|
||||
groupId: latestSparkCoinId.toString(),
|
||||
coin: info.coin,
|
||||
);
|
||||
final sparkUsedCoinTagsFuture =
|
||||
electrumXCachedClient.getSparkUsedCoinsTags(
|
||||
coin: info.coin,
|
||||
);
|
||||
|
||||
// receiving addresses
|
||||
Logging.instance.log(
|
||||
"checking receiving addresses...",
|
||||
|
@ -799,16 +673,29 @@ class FiroWallet extends Bip39HDWallet
|
|||
final futureResults = await Future.wait([
|
||||
usedSerialNumbersFuture,
|
||||
setDataMapFuture,
|
||||
sparkAnonSetFuture,
|
||||
sparkUsedCoinTagsFuture,
|
||||
]);
|
||||
|
||||
// lelantus
|
||||
final usedSerialsSet = (futureResults[0] as List<String>).toSet();
|
||||
final setDataMap = futureResults[1] as Map<dynamic, dynamic>;
|
||||
|
||||
await recoverLelantusWallet(
|
||||
// spark
|
||||
final sparkAnonymitySet = futureResults[2] as Map<String, dynamic>;
|
||||
final sparkSpentCoinTags = futureResults[3] as Set<String>;
|
||||
|
||||
await Future.wait([
|
||||
recoverLelantusWallet(
|
||||
latestSetId: latestSetId,
|
||||
usedSerialNumbers: usedSerialsSet,
|
||||
setDataMap: setDataMap,
|
||||
);
|
||||
),
|
||||
recoverSparkWallet(
|
||||
anonymitySet: sparkAnonymitySet,
|
||||
spentCoinTags: sparkSpentCoinTags,
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
await refresh();
|
||||
|
|
|
@ -37,6 +37,7 @@ import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/electrumx_int
|
|||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
|
||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
|
||||
abstract class Wallet<T extends CryptoCurrency> {
|
||||
// default to Transaction class. For TransactionV2 set to 2
|
||||
|
@ -270,7 +271,7 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
case Coin.firo:
|
||||
return FiroWallet(CryptoCurrencyNetwork.main);
|
||||
case Coin.firoTestNet:
|
||||
return FiroWallet(CryptoCurrencyNetwork.main);
|
||||
return FiroWallet(CryptoCurrencyNetwork.test);
|
||||
|
||||
case Coin.nano:
|
||||
return NanoWallet(CryptoCurrencyNetwork.main);
|
||||
|
@ -289,7 +290,10 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
|
||||
// listen to changes in db and updated wallet info property as required
|
||||
void _watchWalletInfo() {
|
||||
_walletInfoStream = mainDB.isar.walletInfo.watchObject(_walletInfo.id);
|
||||
_walletInfoStream = mainDB.isar.walletInfo.watchObject(
|
||||
_walletInfo.id,
|
||||
fireImmediately: true,
|
||||
);
|
||||
_walletInfoStream.forEach((element) {
|
||||
if (element != null) {
|
||||
_walletInfo = element;
|
||||
|
@ -417,6 +421,10 @@ abstract class Wallet<T extends CryptoCurrency> {
|
|||
.checkChangeAddressForTransactions();
|
||||
}
|
||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId));
|
||||
if (this is SparkInterface) {
|
||||
// this should be called before updateTransactions()
|
||||
await (this as SparkInterface).refreshSparkData();
|
||||
}
|
||||
|
||||
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId));
|
||||
final fetchFuture = updateTransactions();
|
||||
|
|
|
@ -625,9 +625,19 @@ mixin ElectrumXInterface on Bip39HDWallet {
|
|||
// TODO: use coinlib
|
||||
|
||||
final txb = bitcoindart.TransactionBuilder(
|
||||
network: bitcoindart.testnet,
|
||||
network: bitcoindart.NetworkType(
|
||||
messagePrefix: cryptoCurrency.networkParams.messagePrefix,
|
||||
bech32: cryptoCurrency.networkParams.bech32Hrp,
|
||||
bip32: bitcoindart.Bip32Type(
|
||||
public: cryptoCurrency.networkParams.pubHDPrefix,
|
||||
private: cryptoCurrency.networkParams.privHDPrefix,
|
||||
),
|
||||
pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix,
|
||||
scriptHash: cryptoCurrency.networkParams.p2shPrefix,
|
||||
wif: cryptoCurrency.networkParams.wifPrefix,
|
||||
),
|
||||
);
|
||||
txb.setVersion(1);
|
||||
txb.setVersion(1); // TODO possibly override this for certain coins?
|
||||
|
||||
// Add transaction inputs
|
||||
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||
|
@ -1641,7 +1651,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
|
|||
|
||||
@override
|
||||
Future<void> updateUTXOs() async {
|
||||
final allAddresses = await fetchAllOwnAddresses();
|
||||
final allAddresses = await fetchAddressesForElectrumXScan();
|
||||
|
||||
try {
|
||||
final fetchedUtxoList = <List<Map<String, dynamic>>>[];
|
||||
|
@ -1856,7 +1866,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
|
|||
int estimateTxFee({required int vSize, required int feeRatePerKB});
|
||||
Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB);
|
||||
|
||||
Future<List<Address>> fetchAllOwnAddresses();
|
||||
Future<List<Address>> fetchAddressesForElectrumXScan();
|
||||
|
||||
/// Certain coins need to check if the utxo should be marked
|
||||
/// as blocked as well as give a reason.
|
||||
|
|
|
@ -1047,7 +1047,7 @@ mixin LelantusInterface on Bip39HDWallet, ElectrumXInterface {
|
|||
return mints;
|
||||
}
|
||||
|
||||
Future<void> anonymizeAllPublicFunds() async {
|
||||
Future<void> anonymizeAllLelantus() async {
|
||||
try {
|
||||
final mintResult = await _mintSelection();
|
||||
|
||||
|
@ -1056,7 +1056,7 @@ mixin LelantusInterface on Bip39HDWallet, ElectrumXInterface {
|
|||
unawaited(refresh());
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Exception caught in anonymizeAllPublicFunds(): $e\n$s",
|
||||
"Exception caught in anonymizeAllLelantus(): $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
rethrow;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -16,6 +16,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
coinlib_flutter
|
||||
flutter_libsparkmobile
|
||||
tor_ffi_plugin
|
||||
)
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
PODS:
|
||||
- coinlib_flutter (0.3.2):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- connectivity_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- ReachabilitySwift
|
||||
|
@ -22,14 +25,11 @@ PODS:
|
|||
- cw_shared_external (0.0.1):
|
||||
- cw_shared_external/Boost (= 0.0.1)
|
||||
- cw_shared_external/OpenSSL (= 0.0.1)
|
||||
- cw_shared_external/Sodium (= 0.0.1)
|
||||
- FlutterMacOS
|
||||
- cw_shared_external/Boost (0.0.1):
|
||||
- FlutterMacOS
|
||||
- cw_shared_external/OpenSSL (0.0.1):
|
||||
- FlutterMacOS
|
||||
- cw_shared_external/Sodium (0.0.1):
|
||||
- FlutterMacOS
|
||||
- cw_wownero (0.0.1):
|
||||
- cw_wownero/Boost (= 0.0.1)
|
||||
- cw_wownero/OpenSSL (= 0.0.1)
|
||||
|
@ -55,6 +55,8 @@ PODS:
|
|||
- FlutterMacOS
|
||||
- flutter_libepiccash (0.0.1):
|
||||
- FlutterMacOS
|
||||
- flutter_libsparkmobile (0.0.1):
|
||||
- FlutterMacOS
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- FlutterMacOS
|
||||
- flutter_secure_storage_macos (6.1.1):
|
||||
|
@ -74,6 +76,7 @@ PODS:
|
|||
- FlutterMacOS
|
||||
- stack_wallet_backup (0.0.1):
|
||||
- FlutterMacOS
|
||||
- tor_ffi_plugin (0.0.1)
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- wakelock_macos (0.0.1):
|
||||
|
@ -82,6 +85,7 @@ PODS:
|
|||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- coinlib_flutter (from `Flutter/ephemeral/.symlinks/plugins/coinlib_flutter/darwin`)
|
||||
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`)
|
||||
- cw_monero (from `Flutter/ephemeral/.symlinks/plugins/cw_monero/macos`)
|
||||
- cw_shared_external (from `Flutter/ephemeral/.symlinks/plugins/cw_shared_external/macos`)
|
||||
|
@ -90,6 +94,7 @@ DEPENDENCIES:
|
|||
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
|
||||
- devicelocale (from `Flutter/ephemeral/.symlinks/plugins/devicelocale/macos`)
|
||||
- flutter_libepiccash (from `Flutter/ephemeral/.symlinks/plugins/flutter_libepiccash/macos`)
|
||||
- flutter_libsparkmobile (from `Flutter/ephemeral/.symlinks/plugins/flutter_libsparkmobile/macos`)
|
||||
- flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`)
|
||||
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
|
@ -99,6 +104,7 @@ DEPENDENCIES:
|
|||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/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`)
|
||||
- wakelock_macos (from `Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos`)
|
||||
- window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`)
|
||||
|
@ -108,6 +114,8 @@ SPEC REPOS:
|
|||
- ReachabilitySwift
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
coinlib_flutter:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/coinlib_flutter/darwin
|
||||
connectivity_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
|
||||
cw_monero:
|
||||
|
@ -124,6 +132,8 @@ EXTERNAL SOURCES:
|
|||
:path: Flutter/ephemeral/.symlinks/plugins/devicelocale/macos
|
||||
flutter_libepiccash:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_libepiccash/macos
|
||||
flutter_libsparkmobile:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_libsparkmobile/macos
|
||||
flutter_local_notifications:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos
|
||||
flutter_secure_storage_macos:
|
||||
|
@ -142,6 +152,8 @@ EXTERNAL SOURCES:
|
|||
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
||||
stack_wallet_backup:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/stack_wallet_backup/macos
|
||||
tor_ffi_plugin:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/tor_ffi_plugin/macos
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
wakelock_macos:
|
||||
|
@ -150,25 +162,28 @@ EXTERNAL SOURCES:
|
|||
:path: Flutter/ephemeral/.symlinks/plugins/window_size/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
coinlib_flutter: 6abec900d67762a6e7ccfd567a3cd3ae00bbee35
|
||||
connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747
|
||||
cw_monero: a3442556ad3c06365c912735e4a23942a28692b1
|
||||
cw_shared_external: 1f631d1132521baac5f4caed43176fa10d4e0d8b
|
||||
cw_wownero: b4adb1e701fc363de27fa222fcaf4eff6f5fa63a
|
||||
cw_monero: 7acce7238d217e3993ecac6ec2dec07be728769a
|
||||
cw_shared_external: c6adfd29c9be4d64f84e1fa9c541ccbcbdb6b457
|
||||
cw_wownero: bcd7f2ad6c0a3e8e2a51756fb14f0579b6f8b4ff
|
||||
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
|
||||
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
|
||||
devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225
|
||||
flutter_libepiccash: 9113ac75dd325f8bcf00bc3ab583c7fc2780cf3c
|
||||
flutter_libepiccash: be1560a04150c5cc85bcf08d236ec2b3d1f5d8da
|
||||
flutter_libsparkmobile: 8ae86b0ccc7e52c9db6b53e258ee2977deb184ab
|
||||
flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4
|
||||
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
isar_flutter_libs: 43385c99864c168fadba7c9adeddc5d38838ca6a
|
||||
lelantus: 3dfbf92b1e66b3573494dfe3d6a21c4988b5361b
|
||||
lelantus: 308e42c5a648598936a07a234471dd8cf8e687a0
|
||||
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
|
||||
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7
|
||||
stack_wallet_backup: 6ebc60b1bdcf11cf1f1cbad9aa78332e1e15778c
|
||||
url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451
|
||||
tor_ffi_plugin: 2566c1ed174688cca560fa0c64b7a799c66f07cb
|
||||
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
||||
wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9
|
||||
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
|
||||
|
||||
|
|
|
@ -659,6 +659,15 @@ packages:
|
|||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
flutter_libsparkmobile:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "6e2b2650a84fc5832dc676f8d016e530ae77851f"
|
||||
resolved-ref: "6e2b2650a84fc5832dc676f8d016e530ae77851f"
|
||||
url: "https://github.com/cypherstack/flutter_libsparkmobile.git"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
|
11
pubspec.yaml
11
pubspec.yaml
|
@ -27,6 +27,11 @@ dependencies:
|
|||
lelantus:
|
||||
path: ./crypto_plugins/flutter_liblelantus
|
||||
|
||||
flutter_libsparkmobile:
|
||||
git:
|
||||
url: https://github.com/cypherstack/flutter_libsparkmobile.git
|
||||
ref: d54b4a1f492e48696c3df27eb8c31131a681cbc2
|
||||
|
||||
flutter_libmonero:
|
||||
path: ./crypto_plugins/flutter_libmonero
|
||||
|
||||
|
@ -160,7 +165,11 @@ dependencies:
|
|||
url: https://github.com/cypherstack/tezart.git
|
||||
ref: 8a7070f533e63dd150edae99476f6853bfb25913
|
||||
socks5_proxy: ^1.0.3+dev.3
|
||||
coinlib_flutter: ^1.0.0
|
||||
coinlib_flutter:
|
||||
git:
|
||||
url: https://github.com/cypherstack/coinlib.git
|
||||
path: coinlib_flutter
|
||||
ref: 4f549b8b511a63fdc1f44796ab43b10f586635cd
|
||||
convert: ^3.1.1
|
||||
flutter_hooks: ^0.20.3
|
||||
meta: ^1.9.1
|
||||
|
|
|
@ -384,7 +384,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
|
|||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i5.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i5.Future<Map<String, dynamic>> getSparkUsedCoinsTags({
|
||||
_i5.Future<Set<String>> getSparkUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
|
@ -397,9 +397,8 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
|
|||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i5.Future<Map<String, dynamic>>);
|
||||
returnValue: _i5.Future<Set<String>>.value(<String>{}),
|
||||
) as _i5.Future<Set<String>>);
|
||||
@override
|
||||
_i5.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
|
|
|
@ -74,6 +74,25 @@ 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.Coin? coin,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkAnonymitySet,
|
||||
[],
|
||||
{
|
||||
#groupId: groupId,
|
||||
#blockhash: blockhash,
|
||||
#coin: coin,
|
||||
},
|
||||
),
|
||||
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,
|
||||
|
@ -125,6 +144,16 @@ 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.Coin? coin}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
[],
|
||||
{#coin: coin},
|
||||
),
|
||||
returnValue: _i4.Future<Set<String>>.value(<String>{}),
|
||||
) as _i4.Future<Set<String>>);
|
||||
@override
|
||||
_i4.Future<void> clearSharedTransactionCache({required _i5.Coin? coin}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
|
|
@ -381,7 +381,7 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
|
|||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i4.Future<Map<String, dynamic>> getSparkUsedCoinsTags({
|
||||
_i4.Future<Set<String>> getSparkUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
|
@ -394,9 +394,8 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
|
|||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
returnValue: _i4.Future<Set<String>>.value(<String>{}),
|
||||
) as _i4.Future<Set<String>>);
|
||||
@override
|
||||
_i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
|
@ -516,6 +515,25 @@ 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 _i6.Coin? coin,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkAnonymitySet,
|
||||
[],
|
||||
{
|
||||
#groupId: groupId,
|
||||
#blockhash: blockhash,
|
||||
#coin: coin,
|
||||
},
|
||||
),
|
||||
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,
|
||||
|
@ -567,6 +585,16 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
returnValue: _i4.Future<List<String>>.value(<String>[]),
|
||||
) as _i4.Future<List<String>>);
|
||||
@override
|
||||
_i4.Future<Set<String>> getSparkUsedCoinsTags({required _i6.Coin? coin}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
[],
|
||||
{#coin: coin},
|
||||
),
|
||||
returnValue: _i4.Future<Set<String>>.value(<String>{}),
|
||||
) as _i4.Future<Set<String>>);
|
||||
@override
|
||||
_i4.Future<void> clearSharedTransactionCache({required _i6.Coin? coin}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
|
|
@ -381,7 +381,7 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
|
|||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i4.Future<Map<String, dynamic>> getSparkUsedCoinsTags({
|
||||
_i4.Future<Set<String>> getSparkUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
|
@ -394,9 +394,8 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
|
|||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
returnValue: _i4.Future<Set<String>>.value(<String>{}),
|
||||
) as _i4.Future<Set<String>>);
|
||||
@override
|
||||
_i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
|
@ -516,6 +515,25 @@ 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 _i6.Coin? coin,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkAnonymitySet,
|
||||
[],
|
||||
{
|
||||
#groupId: groupId,
|
||||
#blockhash: blockhash,
|
||||
#coin: coin,
|
||||
},
|
||||
),
|
||||
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,
|
||||
|
@ -567,6 +585,16 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
returnValue: _i4.Future<List<String>>.value(<String>[]),
|
||||
) as _i4.Future<List<String>>);
|
||||
@override
|
||||
_i4.Future<Set<String>> getSparkUsedCoinsTags({required _i6.Coin? coin}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
[],
|
||||
{#coin: coin},
|
||||
),
|
||||
returnValue: _i4.Future<Set<String>>.value(<String>{}),
|
||||
) as _i4.Future<Set<String>>);
|
||||
@override
|
||||
_i4.Future<void> clearSharedTransactionCache({required _i6.Coin? coin}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
|
|
@ -381,7 +381,7 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
|
|||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i4.Future<Map<String, dynamic>> getSparkUsedCoinsTags({
|
||||
_i4.Future<Set<String>> getSparkUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
|
@ -394,9 +394,8 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
|
|||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
returnValue: _i4.Future<Set<String>>.value(<String>{}),
|
||||
) as _i4.Future<Set<String>>);
|
||||
@override
|
||||
_i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
|
@ -516,6 +515,25 @@ 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 _i6.Coin? coin,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkAnonymitySet,
|
||||
[],
|
||||
{
|
||||
#groupId: groupId,
|
||||
#blockhash: blockhash,
|
||||
#coin: coin,
|
||||
},
|
||||
),
|
||||
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,
|
||||
|
@ -567,6 +585,16 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
returnValue: _i4.Future<List<String>>.value(<String>[]),
|
||||
) as _i4.Future<List<String>>);
|
||||
@override
|
||||
_i4.Future<Set<String>> getSparkUsedCoinsTags({required _i6.Coin? coin}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
[],
|
||||
{#coin: coin},
|
||||
),
|
||||
returnValue: _i4.Future<Set<String>>.value(<String>{}),
|
||||
) as _i4.Future<Set<String>>);
|
||||
@override
|
||||
_i4.Future<void> clearSharedTransactionCache({required _i6.Coin? coin}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
|
|
@ -411,7 +411,7 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
|
|||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i5.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i5.Future<Map<String, dynamic>> getSparkUsedCoinsTags({
|
||||
_i5.Future<Set<String>> getSparkUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
|
@ -424,9 +424,8 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
|
|||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i5.Future<Map<String, dynamic>>);
|
||||
returnValue: _i5.Future<Set<String>>.value(<String>{}),
|
||||
) as _i5.Future<Set<String>>);
|
||||
@override
|
||||
_i5.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
|
@ -546,6 +545,25 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i5.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i5.Future<Map<String, dynamic>> getSparkAnonymitySet({
|
||||
required String? groupId,
|
||||
String? blockhash = r'',
|
||||
required _i7.Coin? coin,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkAnonymitySet,
|
||||
[],
|
||||
{
|
||||
#groupId: groupId,
|
||||
#blockhash: blockhash,
|
||||
#coin: coin,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i5.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
String base64ToHex(String? source) => (super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#base64ToHex,
|
||||
|
@ -597,6 +615,16 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
returnValue: _i5.Future<List<String>>.value(<String>[]),
|
||||
) as _i5.Future<List<String>>);
|
||||
@override
|
||||
_i5.Future<Set<String>> getSparkUsedCoinsTags({required _i7.Coin? coin}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
[],
|
||||
{#coin: coin},
|
||||
),
|
||||
returnValue: _i5.Future<Set<String>>.value(<String>{}),
|
||||
) as _i5.Future<Set<String>>);
|
||||
@override
|
||||
_i5.Future<void> clearSharedTransactionCache({required _i7.Coin? coin}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
|
|
@ -381,7 +381,7 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
|
|||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i4.Future<Map<String, dynamic>> getSparkUsedCoinsTags({
|
||||
_i4.Future<Set<String>> getSparkUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
|
@ -394,9 +394,8 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
|
|||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
returnValue: _i4.Future<Set<String>>.value(<String>{}),
|
||||
) as _i4.Future<Set<String>>);
|
||||
@override
|
||||
_i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
|
@ -516,6 +515,25 @@ 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 _i6.Coin? coin,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkAnonymitySet,
|
||||
[],
|
||||
{
|
||||
#groupId: groupId,
|
||||
#blockhash: blockhash,
|
||||
#coin: coin,
|
||||
},
|
||||
),
|
||||
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,
|
||||
|
@ -567,6 +585,16 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
returnValue: _i4.Future<List<String>>.value(<String>[]),
|
||||
) as _i4.Future<List<String>>);
|
||||
@override
|
||||
_i4.Future<Set<String>> getSparkUsedCoinsTags({required _i6.Coin? coin}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
[],
|
||||
{#coin: coin},
|
||||
),
|
||||
returnValue: _i4.Future<Set<String>>.value(<String>{}),
|
||||
) as _i4.Future<Set<String>>);
|
||||
@override
|
||||
_i4.Future<void> clearSharedTransactionCache({required _i6.Coin? coin}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
|
|
@ -381,7 +381,7 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
|
|||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
@override
|
||||
_i4.Future<Map<String, dynamic>> getSparkUsedCoinsTags({
|
||||
_i4.Future<Set<String>> getSparkUsedCoinsTags({
|
||||
String? requestID,
|
||||
required int? startNumber,
|
||||
}) =>
|
||||
|
@ -394,9 +394,8 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
|
|||
#startNumber: startNumber,
|
||||
},
|
||||
),
|
||||
returnValue:
|
||||
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i4.Future<Map<String, dynamic>>);
|
||||
returnValue: _i4.Future<Set<String>>.value(<String>{}),
|
||||
) as _i4.Future<Set<String>>);
|
||||
@override
|
||||
_i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
|
||||
String? requestID,
|
||||
|
@ -516,6 +515,25 @@ 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 _i6.Coin? coin,
|
||||
}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkAnonymitySet,
|
||||
[],
|
||||
{
|
||||
#groupId: groupId,
|
||||
#blockhash: blockhash,
|
||||
#coin: coin,
|
||||
},
|
||||
),
|
||||
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,
|
||||
|
@ -567,6 +585,16 @@ class MockCachedElectrumXClient extends _i1.Mock
|
|||
returnValue: _i4.Future<List<String>>.value(<String>[]),
|
||||
) as _i4.Future<List<String>>);
|
||||
@override
|
||||
_i4.Future<Set<String>> getSparkUsedCoinsTags({required _i6.Coin? coin}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#getSparkUsedCoinsTags,
|
||||
[],
|
||||
{#coin: coin},
|
||||
),
|
||||
returnValue: _i4.Future<Set<String>>.value(<String>{}),
|
||||
) as _i4.Future<Set<String>>);
|
||||
@override
|
||||
_i4.Future<void> clearSharedTransactionCache({required _i6.Coin? coin}) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
|
|
|
@ -16,6 +16,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
flutter_libsparkmobile
|
||||
tor_ffi_plugin
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue