Merge pull request #713 from cypherstack/wallets_refactor_spark_integrationn

firo spark added
This commit is contained in:
julian-CStack 2024-01-04 10:59:22 -06:00 committed by GitHub
commit 8a42287a77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 8537 additions and 2033 deletions

View file

@ -53,8 +53,12 @@ class DB {
// firo only // firo only
String _boxNameSetCache({required Coin coin}) => String _boxNameSetCache({required Coin coin}) =>
"${coin.name}_anonymitySetCache"; "${coin.name}_anonymitySetCache";
String _boxNameSetSparkCache({required Coin coin}) =>
"${coin.name}_anonymitySetSparkCache";
String _boxNameUsedSerialsCache({required Coin coin}) => String _boxNameUsedSerialsCache({required Coin coin}) =>
"${coin.name}_usedSerialsCache"; "${coin.name}_usedSerialsCache";
String _boxNameSparkUsedCoinsTagsCache({required Coin coin}) =>
"${coin.name}_sparkUsedCoinsTagsCache";
Box<NodeModel>? _boxNodeModels; Box<NodeModel>? _boxNodeModels;
Box<NodeModel>? _boxPrimaryNodes; Box<NodeModel>? _boxPrimaryNodes;
@ -75,7 +79,9 @@ class DB {
final Map<Coin, Box<dynamic>> _txCacheBoxes = {}; final Map<Coin, Box<dynamic>> _txCacheBoxes = {};
final Map<Coin, Box<dynamic>> _setCacheBoxes = {}; final Map<Coin, Box<dynamic>> _setCacheBoxes = {};
final Map<Coin, Box<dynamic>> _setSparkCacheBoxes = {};
final Map<Coin, Box<dynamic>> _usedSerialsCacheBoxes = {}; final Map<Coin, Box<dynamic>> _usedSerialsCacheBoxes = {};
final Map<Coin, Box<dynamic>> _getSparkUsedCoinsTagsCacheBoxes = {};
// exposed for monero // exposed for monero
Box<xmr.WalletInfo> get moneroWalletInfoBox => _walletInfoSource!; Box<xmr.WalletInfo> get moneroWalletInfoBox => _walletInfoSource!;
@ -197,6 +203,15 @@ class DB {
await Hive.openBox<dynamic>(_boxNameSetCache(coin: coin)); 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 { Future<void> closeAnonymitySetCacheBox({required Coin coin}) async {
await _setCacheBoxes[coin]?.close(); await _setCacheBoxes[coin]?.close();
} }
@ -209,6 +224,16 @@ class DB {
await Hive.openBox<dynamic>(_boxNameUsedSerialsCache(coin: coin)); 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 { Future<void> closeUsedSerialsCacheBox({required Coin coin}) async {
await _usedSerialsCacheBoxes[coin]?.close(); await _usedSerialsCacheBoxes[coin]?.close();
} }
@ -216,9 +241,12 @@ class DB {
/// Clear all cached transactions for the specified coin /// Clear all cached transactions for the specified coin
Future<void> clearSharedTransactionCache({required Coin coin}) async { Future<void> clearSharedTransactionCache({required Coin coin}) async {
await deleteAll<dynamic>(boxName: _boxNameTxCache(coin: coin)); 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: _boxNameSetCache(coin: coin));
await deleteAll<dynamic>(boxName: _boxNameSetSparkCache(coin: coin));
await deleteAll<dynamic>(boxName: _boxNameUsedSerialsCache(coin: coin)); await deleteAll<dynamic>(boxName: _boxNameUsedSerialsCache(coin: coin));
await deleteAll<dynamic>(
boxName: _boxNameSparkUsedCoinsTagsCache(coin: coin));
} }
} }

View file

@ -21,6 +21,7 @@ import 'package:stackwallet/models/isar/stack_theme.dart';
import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/stack_file_system.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:stackwallet/wallets/isar/models/wallet_info.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
@ -61,6 +62,7 @@ class MainDB {
LelantusCoinSchema, LelantusCoinSchema,
WalletInfoSchema, WalletInfoSchema,
TransactionV2Schema, TransactionV2Schema,
SparkCoinSchema,
], ],
directory: (await StackFileSystem.applicationIsarDirectory()).path, directory: (await StackFileSystem.applicationIsarDirectory()).path,
// inspector: kDebugMode, // inspector: kDebugMode,
@ -482,6 +484,12 @@ class MainDB {
// .findAll(); // .findAll();
// await isar.lelantusCoins.deleteAll(lelantusCoinIds); // await isar.lelantusCoins.deleteAll(lelantusCoinIds);
// } // }
// spark coins
await isar.sparkCoins
.where()
.walletIdEqualToAnyLTagHash(walletId)
.deleteAll();
}); });
} }

View file

@ -13,6 +13,7 @@ import 'package:stackwallet/wallets/wallet/supporting/epiccash_wallet_info_exten
Future<void> migrateWalletsToIsar({ Future<void> migrateWalletsToIsar({
required SecureStorageInterface secureStore, required SecureStorageInterface secureStore,
}) async { }) async {
await MainDB.instance.initMainDB();
final allWalletsBox = await Hive.openBox<dynamic>(DB.boxNameAllWalletsData); final allWalletsBox = await Hive.openBox<dynamic>(DB.boxNameAllWalletsData);
final names = DB.instance final names = DB.instance

View file

@ -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) => String base64ToHex(String source) =>
base64Decode(LineSplitter.split(source).join()) base64Decode(LineSplitter.split(source).join())
.map((e) => e.toRadixString(16).padLeft(2, '0')) .map((e) => e.toRadixString(16).padLeft(2, '0'))
@ -136,6 +193,7 @@ class CachedElectrumXClient {
result.remove("hex"); result.remove("hex");
result.remove("lelantusData"); result.remove("lelantusData");
result.remove("sparkData");
if (result["confirmations"] != null && if (result["confirmations"] != null &&
result["confirmations"] as int > minCacheConfirms) { result["confirmations"] as int > minCacheConfirms) {
@ -198,14 +256,62 @@ class CachedElectrumXClient {
return resultingList; return resultingList;
} catch (e, s) { } catch (e, s) {
Logging.instance.log( Logging.instance.log(
"Failed to process CachedElectrumX.getTransaction(): $e\n$s", "Failed to process CachedElectrumX.getUsedCoinSerials(): $e\n$s",
level: LogLevel.Error); 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; rethrow;
} }
} }
/// Clear all cached transactions for the specified coin /// Clear all cached transactions for the specified coin
Future<void> clearSharedTransactionCache({required Coin coin}) async { Future<void> clearSharedTransactionCache({required Coin coin}) async {
await DB.instance.clearSharedTransactionCache(coin: coin);
await DB.instance.closeAnonymitySetCacheBox(coin: coin); await DB.instance.closeAnonymitySetCacheBox(coin: coin);
} }
} }

View file

@ -15,6 +15,8 @@ import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:decimal/decimal.dart'; import 'package:decimal/decimal.dart';
import 'package:event_bus/event_bus.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:mutex/mutex.dart';
import 'package:stackwallet/electrumx_rpc/rpc.dart'; import 'package:stackwallet/electrumx_rpc/rpc.dart';
import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart'; import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart';
@ -467,9 +469,9 @@ class ElectrumXClient {
/// and the binary header as a hexadecimal string. /// and the binary header as a hexadecimal string.
/// Ex: /// Ex:
/// { /// {
// "height": 520481, /// "height": 520481,
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4" /// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
// } /// }
Future<Map<String, dynamic>> getBlockHeadTip({String? requestID}) async { Future<Map<String, dynamic>> getBlockHeadTip({String? requestID}) async {
try { try {
final response = await request( final response = await request(
@ -493,15 +495,15 @@ class ElectrumXClient {
/// ///
/// Returns a map with server information /// Returns a map with server information
/// Ex: /// Ex:
// { /// {
// "genesis_hash": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943", /// "genesis_hash": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
// "hosts": {"14.3.140.101": {"tcp_port": 51001, "ssl_port": 51002}}, /// "hosts": {"14.3.140.101": {"tcp_port": 51001, "ssl_port": 51002}},
// "protocol_max": "1.0", /// "protocol_max": "1.0",
// "protocol_min": "1.0", /// "protocol_min": "1.0",
// "pruning": null, /// "pruning": null,
// "server_version": "ElectrumX 1.0.17", /// "server_version": "ElectrumX 1.0.17",
// "hash_function": "sha256" /// "hash_function": "sha256"
// } /// }
Future<Map<String, dynamic>> getServerFeatures({String? requestID}) async { Future<Map<String, dynamic>> getServerFeatures({String? requestID}) async {
try { try {
final response = await request( 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. /// Returns a list of maps that contain the tx_hash and height of the tx.
/// Ex: /// Ex:
/// [ /// [
// { /// {
// "height": 200004, /// "height": 200004,
// "tx_hash": "acc3758bd2a26f869fcc67d48ff30b96464d476bca82c1cd6656e7d506816412" /// "tx_hash": "acc3758bd2a26f869fcc67d48ff30b96464d476bca82c1cd6656e7d506816412"
// }, /// },
// { /// {
// "height": 215008, /// "height": 215008,
// "tx_hash": "f3e1bf48975b8d6060a9de8884296abb80be618dc00ae3cb2f6cee3085e09403" /// "tx_hash": "f3e1bf48975b8d6060a9de8884296abb80be618dc00ae3cb2f6cee3085e09403"
// } /// }
// ] /// ]
Future<List<Map<String, dynamic>>> getHistory({ Future<List<Map<String, dynamic>>> getHistory({
required String scripthash, required String scripthash,
String? requestID, String? requestID,
@ -627,19 +629,19 @@ class ElectrumXClient {
/// Returns a list of maps. /// Returns a list of maps.
/// Ex: /// Ex:
/// [ /// [
// { /// {
// "tx_pos": 0, /// "tx_pos": 0,
// "value": 45318048, /// "value": 45318048,
// "tx_hash": "9f2c45a12db0144909b5db269415f7319179105982ac70ed80d76ea79d923ebf", /// "tx_hash": "9f2c45a12db0144909b5db269415f7319179105982ac70ed80d76ea79d923ebf",
// "height": 437146 /// "height": 437146
// }, /// },
// { /// {
// "tx_pos": 0, /// "tx_pos": 0,
// "value": 919195, /// "value": 919195,
// "tx_hash": "3d2290c93436a3e964cfc2f0950174d8847b1fbe3946432c4784e168da0f019f", /// "tx_hash": "3d2290c93436a3e964cfc2f0950174d8847b1fbe3946432c4784e168da0f019f",
// "height": 441696 /// "height": 441696
// } /// }
// ] /// ]
Future<List<Map<String, dynamic>>> getUTXOs({ Future<List<Map<String, dynamic>>> getUTXOs({
required String scripthash, required String scripthash,
String? requestID, String? requestID,
@ -881,7 +883,7 @@ class ElectrumXClient {
/// ///
/// Returns blockHash (last block hash), /// Returns blockHash (last block hash),
/// setHash (hash of current set) /// 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({ Future<Map<String, dynamic>> getSparkAnonymitySet({
String coinGroupId = "1", String coinGroupId = "1",
String startBlockHash = "", String startBlockHash = "",
@ -908,7 +910,7 @@ class ElectrumXClient {
/// Takes [startNumber], if it is 0, we get the full set, /// Takes [startNumber], if it is 0, we get the full set,
/// otherwise the used tags after that number /// otherwise the used tags after that number
Future<Map<String, dynamic>> getSparkUsedCoinsTags({ Future<Set<String>> getSparkUsedCoinsTags({
String? requestID, String? requestID,
required int startNumber, required int startNumber,
}) async { }) async {
@ -921,7 +923,9 @@ class ElectrumXClient {
], ],
requestTimeout: const Duration(minutes: 2), 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) { } catch (e) {
Logging.instance.log(e, level: LogLevel.Error); Logging.instance.log(e, level: LogLevel.Error);
rethrow; rethrow;
@ -984,8 +988,8 @@ class ElectrumXClient {
/// Returns a map with the kay "rate" that corresponds to the free rate in satoshis /// Returns a map with the kay "rate" that corresponds to the free rate in satoshis
/// Ex: /// Ex:
/// { /// {
// "rate": 1000, /// "rate": 1000,
// } /// }
Future<Map<String, dynamic>> getFeeRate({String? requestID}) async { Future<Map<String, dynamic>> getFeeRate({String? requestID}) async {
try { try {
final response = await request( final response = await request(
@ -1035,3 +1039,7 @@ class ElectrumXClient {
} }
} }
} }
Set<String> _ffiHashTagsComputeWrapper(Set<String> base64Tags) {
return LibSpark.hashTags(base64Tags: base64Tags);
}

View file

@ -72,6 +72,7 @@ import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/utilities/stack_file_system.dart'; import 'package:stackwallet/utilities/stack_file_system.dart';
import 'package:stackwallet/utilities/util.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:stackwallet/widgets/crypto_notifications.dart';
import 'package:window_size/window_size.dart'; import 'package:window_size/window_size.dart';
@ -747,7 +748,7 @@ class _MaterialAppWithThemeState extends ConsumerState<MaterialAppWithTheme>
builder: (BuildContext context, AsyncSnapshot<void> snapshot) { builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
// FlutterNativeSplash.remove(); // FlutterNativeSplash.remove();
if (ref.read(pWallets).hasWallets || if (ref.read(pAllWalletsInfo).isNotEmpty ||
ref.read(prefsChangeNotifierProvider).hasPin) { ref.read(prefsChangeNotifierProvider).hasPin) {
// return HomeView(); // return HomeView();

View file

@ -252,5 +252,7 @@ enum TransactionSubType {
mint, // firo specific mint, // firo specific
join, // firo specific join, // firo specific
ethToken, // eth token ethToken, // eth token
cashFusion; cashFusion,
sparkMint, // firo specific
sparkSpend; // firo specific
} }

View file

@ -365,6 +365,8 @@ const _TransactionsubTypeEnumValueMap = {
'join': 3, 'join': 3,
'ethToken': 4, 'ethToken': 4,
'cashFusion': 5, 'cashFusion': 5,
'sparkMint': 6,
'sparkSpend': 7,
}; };
const _TransactionsubTypeValueEnumMap = { const _TransactionsubTypeValueEnumMap = {
0: TransactionSubType.none, 0: TransactionSubType.none,
@ -373,6 +375,8 @@ const _TransactionsubTypeValueEnumMap = {
3: TransactionSubType.join, 3: TransactionSubType.join,
4: TransactionSubType.ethToken, 4: TransactionSubType.ethToken,
5: TransactionSubType.cashFusion, 5: TransactionSubType.cashFusion,
6: TransactionSubType.sparkMint,
7: TransactionSubType.sparkSpend,
}; };
const _TransactiontypeEnumValueMap = { const _TransactiontypeEnumValueMap = {
'outgoing': 0, 'outgoing': 0,

View file

@ -46,7 +46,7 @@ class OutputV2 {
Map<String, dynamic> json, { Map<String, dynamic> json, {
required bool walletOwns, required bool walletOwns,
required int decimalPlaces, required int decimalPlaces,
bool isECashFullAmountNotSats = false, bool isFullAmountNotSats = false,
}) { }) {
try { try {
List<String> addresses = []; List<String> addresses = [];
@ -61,9 +61,11 @@ class OutputV2 {
return OutputV2.isarCantDoRequiredInDefaultConstructor( return OutputV2.isarCantDoRequiredInDefaultConstructor(
scriptPubKeyHex: json["scriptPubKey"]["hex"] as String, scriptPubKeyHex: json["scriptPubKey"]["hex"] as String,
valueStringSats: parseOutputAmountString(json["value"].toString(), valueStringSats: parseOutputAmountString(
json["value"].toString(),
decimalPlaces: decimalPlaces, decimalPlaces: decimalPlaces,
isECashFullAmountNotSats: isECashFullAmountNotSats), isFullAmountNotSats: isFullAmountNotSats,
),
addresses: addresses, addresses: addresses,
walletOwns: walletOwns, walletOwns: walletOwns,
); );
@ -75,7 +77,7 @@ class OutputV2 {
static String parseOutputAmountString( static String parseOutputAmountString(
String amount, { String amount, {
required int decimalPlaces, required int decimalPlaces,
bool isECashFullAmountNotSats = false, bool isFullAmountNotSats = false,
}) { }) {
final temp = Decimal.parse(amount); final temp = Decimal.parse(amount);
if (temp < Decimal.zero) { if (temp < Decimal.zero) {
@ -83,7 +85,7 @@ class OutputV2 {
} }
final String valueStringSats; final String valueStringSats;
if (isECashFullAmountNotSats) { if (isFullAmountNotSats) {
valueStringSats = temp.shift(decimalPlaces).toBigInt().toString(); valueStringSats = temp.shift(decimalPlaces).toBigInt().toString();
} else if (temp.isInteger) { } else if (temp.isInteger) {
valueStringSats = temp.toString(); valueStringSats = temp.toString();

View file

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:isar/isar.dart'; 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/models/isar/models/blockchain_data/v2/output_v2.dart';
import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/coin_enum.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'; part 'transaction_v2.g.dart';
@ -37,6 +40,8 @@ class TransactionV2 {
@enumerated @enumerated
final TransactionSubType subType; final TransactionSubType subType;
final String? otherData;
TransactionV2({ TransactionV2({
required this.walletId, required this.walletId,
required this.blockHash, required this.blockHash,
@ -49,6 +54,7 @@ class TransactionV2 {
required this.version, required this.version,
required this.type, required this.type,
required this.subType, required this.subType,
required this.otherData,
}); });
int getConfirmations(int currentChainHeight) { int getConfirmations(int currentChainHeight) {
@ -71,7 +77,7 @@ class TransactionV2 {
return Amount(rawValue: inSum - outSum, fractionDigits: coin.decimals); return Amount(rawValue: inSum - outSum, fractionDigits: coin.decimals);
} }
Amount getAmountReceivedThisWallet({required Coin coin}) { Amount getAmountReceivedInThisWallet({required Coin coin}) {
final outSum = outputs final outSum = outputs
.where((e) => e.walletOwns) .where((e) => e.walletOwns)
.fold(BigInt.zero, (p, e) => p + e.value); .fold(BigInt.zero, (p, e) => p + e.value);
@ -79,12 +85,28 @@ class TransactionV2 {
return Amount(rawValue: outSum, fractionDigits: coin.decimals); 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}) { Amount getAmountSentFromThisWallet({required Coin coin}) {
final inSum = inputs final inSum = inputs
.where((e) => e.walletOwns) .where((e) => e.walletOwns)
.fold(BigInt.zero, (p, e) => p + e.value); .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() => { Set<String> associatedAddresses() => {
@ -92,6 +114,82 @@ class TransactionV2 {
...outputs.map((e) => e.addresses).expand((e) => e), ...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 @override
String toString() { String toString() {
return 'TransactionV2(\n' return 'TransactionV2(\n'
@ -106,6 +204,7 @@ class TransactionV2 {
' version: $version,\n' ' version: $version,\n'
' inputs: $inputs,\n' ' inputs: $inputs,\n'
' outputs: $outputs,\n' ' outputs: $outputs,\n'
' otherData: $otherData,\n'
')'; ')';
} }
} }

View file

@ -38,41 +38,46 @@ const TransactionV2Schema = CollectionSchema(
type: IsarType.objectList, type: IsarType.objectList,
target: r'InputV2', target: r'InputV2',
), ),
r'outputs': PropertySchema( r'otherData': PropertySchema(
id: 4, id: 4,
name: r'otherData',
type: IsarType.string,
),
r'outputs': PropertySchema(
id: 5,
name: r'outputs', name: r'outputs',
type: IsarType.objectList, type: IsarType.objectList,
target: r'OutputV2', target: r'OutputV2',
), ),
r'subType': PropertySchema( r'subType': PropertySchema(
id: 5, id: 6,
name: r'subType', name: r'subType',
type: IsarType.byte, type: IsarType.byte,
enumMap: _TransactionV2subTypeEnumValueMap, enumMap: _TransactionV2subTypeEnumValueMap,
), ),
r'timestamp': PropertySchema( r'timestamp': PropertySchema(
id: 6, id: 7,
name: r'timestamp', name: r'timestamp',
type: IsarType.long, type: IsarType.long,
), ),
r'txid': PropertySchema( r'txid': PropertySchema(
id: 7, id: 8,
name: r'txid', name: r'txid',
type: IsarType.string, type: IsarType.string,
), ),
r'type': PropertySchema( r'type': PropertySchema(
id: 8, id: 9,
name: r'type', name: r'type',
type: IsarType.byte, type: IsarType.byte,
enumMap: _TransactionV2typeEnumValueMap, enumMap: _TransactionV2typeEnumValueMap,
), ),
r'version': PropertySchema( r'version': PropertySchema(
id: 9, id: 10,
name: r'version', name: r'version',
type: IsarType.long, type: IsarType.long,
), ),
r'walletId': PropertySchema( r'walletId': PropertySchema(
id: 10, id: 11,
name: r'walletId', name: r'walletId',
type: IsarType.string, type: IsarType.string,
) )
@ -161,6 +166,12 @@ int _transactionV2EstimateSize(
bytesCount += InputV2Schema.estimateSize(value, offsets, allOffsets); bytesCount += InputV2Schema.estimateSize(value, offsets, allOffsets);
} }
} }
{
final value = object.otherData;
if (value != null) {
bytesCount += 3 + value.length * 3;
}
}
bytesCount += 3 + object.outputs.length * 3; bytesCount += 3 + object.outputs.length * 3;
{ {
final offsets = allOffsets[OutputV2]!; final offsets = allOffsets[OutputV2]!;
@ -189,18 +200,19 @@ void _transactionV2Serialize(
InputV2Schema.serialize, InputV2Schema.serialize,
object.inputs, object.inputs,
); );
writer.writeString(offsets[4], object.otherData);
writer.writeObjectList<OutputV2>( writer.writeObjectList<OutputV2>(
offsets[4], offsets[5],
allOffsets, allOffsets,
OutputV2Schema.serialize, OutputV2Schema.serialize,
object.outputs, object.outputs,
); );
writer.writeByte(offsets[5], object.subType.index); writer.writeByte(offsets[6], object.subType.index);
writer.writeLong(offsets[6], object.timestamp); writer.writeLong(offsets[7], object.timestamp);
writer.writeString(offsets[7], object.txid); writer.writeString(offsets[8], object.txid);
writer.writeByte(offsets[8], object.type.index); writer.writeByte(offsets[9], object.type.index);
writer.writeLong(offsets[9], object.version); writer.writeLong(offsets[10], object.version);
writer.writeString(offsets[10], object.walletId); writer.writeString(offsets[11], object.walletId);
} }
TransactionV2 _transactionV2Deserialize( TransactionV2 _transactionV2Deserialize(
@ -220,22 +232,23 @@ TransactionV2 _transactionV2Deserialize(
InputV2(), InputV2(),
) ?? ) ??
[], [],
otherData: reader.readStringOrNull(offsets[4]),
outputs: reader.readObjectList<OutputV2>( outputs: reader.readObjectList<OutputV2>(
offsets[4], offsets[5],
OutputV2Schema.deserialize, OutputV2Schema.deserialize,
allOffsets, allOffsets,
OutputV2(), OutputV2(),
) ?? ) ??
[], [],
subType: subType:
_TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[5])] ?? _TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[6])] ??
TransactionSubType.none, TransactionSubType.none,
timestamp: reader.readLong(offsets[6]), timestamp: reader.readLong(offsets[7]),
txid: reader.readString(offsets[7]), txid: reader.readString(offsets[8]),
type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[8])] ?? type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[9])] ??
TransactionType.outgoing, TransactionType.outgoing,
version: reader.readLong(offsets[9]), version: reader.readLong(offsets[10]),
walletId: reader.readString(offsets[10]), walletId: reader.readString(offsets[11]),
); );
object.id = id; object.id = id;
return object; return object;
@ -263,6 +276,8 @@ P _transactionV2DeserializeProp<P>(
) ?? ) ??
[]) as P; []) as P;
case 4: case 4:
return (reader.readStringOrNull(offset)) as P;
case 5:
return (reader.readObjectList<OutputV2>( return (reader.readObjectList<OutputV2>(
offset, offset,
OutputV2Schema.deserialize, OutputV2Schema.deserialize,
@ -270,20 +285,20 @@ P _transactionV2DeserializeProp<P>(
OutputV2(), OutputV2(),
) ?? ) ??
[]) as P; []) as P;
case 5: case 6:
return (_TransactionV2subTypeValueEnumMap[ return (_TransactionV2subTypeValueEnumMap[
reader.readByteOrNull(offset)] ?? reader.readByteOrNull(offset)] ??
TransactionSubType.none) as P; TransactionSubType.none) as P;
case 6:
return (reader.readLong(offset)) as P;
case 7: case 7:
return (reader.readString(offset)) as P; return (reader.readLong(offset)) as P;
case 8: case 8:
return (reader.readString(offset)) as P;
case 9:
return (_TransactionV2typeValueEnumMap[reader.readByteOrNull(offset)] ?? return (_TransactionV2typeValueEnumMap[reader.readByteOrNull(offset)] ??
TransactionType.outgoing) as P; TransactionType.outgoing) as P;
case 9:
return (reader.readLong(offset)) as P;
case 10: case 10:
return (reader.readLong(offset)) as P;
case 11:
return (reader.readString(offset)) as P; return (reader.readString(offset)) as P;
default: default:
throw IsarError('Unknown property with id $propertyId'); throw IsarError('Unknown property with id $propertyId');
@ -297,6 +312,8 @@ const _TransactionV2subTypeEnumValueMap = {
'join': 3, 'join': 3,
'ethToken': 4, 'ethToken': 4,
'cashFusion': 5, 'cashFusion': 5,
'sparkMint': 6,
'sparkSpend': 7,
}; };
const _TransactionV2subTypeValueEnumMap = { const _TransactionV2subTypeValueEnumMap = {
0: TransactionSubType.none, 0: TransactionSubType.none,
@ -305,6 +322,8 @@ const _TransactionV2subTypeValueEnumMap = {
3: TransactionSubType.join, 3: TransactionSubType.join,
4: TransactionSubType.ethToken, 4: TransactionSubType.ethToken,
5: TransactionSubType.cashFusion, 5: TransactionSubType.cashFusion,
6: TransactionSubType.sparkMint,
7: TransactionSubType.sparkSpend,
}; };
const _TransactionV2typeEnumValueMap = { const _TransactionV2typeEnumValueMap = {
'outgoing': 0, '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> QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
outputsLengthEqualTo(int length) { outputsLengthEqualTo(int length) {
return QueryBuilder.apply(this, (query) { 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() { QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortBySubType() {
return QueryBuilder.apply(this, (query) { return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'subType', Sort.asc); 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() { QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenBySubType() {
return QueryBuilder.apply(this, (query) { return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'subType', Sort.asc); 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() { QueryBuilder<TransactionV2, TransactionV2, QDistinct> distinctBySubType() {
return QueryBuilder.apply(this, (query) { return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'subType'); 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> QueryBuilder<TransactionV2, List<OutputV2>, QQueryOperations>
outputsProperty() { outputsProperty() {
return QueryBuilder.apply(this, (query) { return QueryBuilder.apply(this, (query) {

View file

@ -126,7 +126,7 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
void initState() { void initState() {
_searchFieldController = TextEditingController(); _searchFieldController = TextEditingController();
_searchFocusNode = FocusNode(); _searchFocusNode = FocusNode();
_coinsTestnet.remove(Coin.firoTestNet); // _coinsTestnet.remove(Coin.firoTestNet);
if (Platform.isWindows) { if (Platform.isWindows) {
_coins.remove(Coin.monero); _coins.remove(Coin.monero);
_coins.remove(Coin.wownero); _coins.remove(Coin.wownero);

View file

@ -352,6 +352,16 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
data: address.derivationPath!.value, data: address.derivationPath!.value,
button: Container(), 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( const _Div(
height: 12, height: 12,
), ),

View file

@ -10,15 +10,18 @@
import 'dart:async'; import 'dart:async';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:isar/isar.dart';
import 'package:qr_flutter/qr_flutter.dart'; import 'package:qr_flutter/qr_flutter.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/notifications/show_flush_bar.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/addresses/wallet_addresses_view.dart';
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_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/providers/providers.dart';
import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/themes/stack_colors.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/utilities/text_styles.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.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/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/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/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart';
@ -58,6 +63,11 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
late final Coin coin; late final Coin coin;
late final String walletId; late final String walletId;
late final ClipboardInterface clipboard; late final ClipboardInterface clipboard;
late final bool supportsSpark;
String? _sparkAddress;
String? _qrcodeContent;
bool _showSparkAddress = true;
Future<void> generateNewAddress() async { Future<void> generateNewAddress() async {
final wallet = ref.read(pWallets).getWallet(walletId); 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 @override
void initState() { void initState() {
walletId = widget.walletId; walletId = widget.walletId;
coin = ref.read(pWalletCoin(walletId)); coin = ref.read(pWalletCoin(walletId));
clipboard = widget.clipboard; 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(); super.initState();
} }
@override
void dispose() {
_streamSub?.cancel();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType"); debugPrint("BUILD: $runtimeType");
final receivingAddress = ref.watch(pWalletReceivingAddress(walletId));
final ticker = widget.tokenContract?.symbol ?? coin.ticker; 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( return Background(
child: Scaffold( child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background, backgroundColor: Theme.of(context).extension<StackColors>()!.background,
@ -225,11 +318,166 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ 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( 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: () { onTap: () {
HapticFeedback.lightImpact(); HapticFeedback.lightImpact();
clipboard.setData( clipboard.setData(
ClipboardData(text: receivingAddress), ClipboardData(
text:
ref.watch(pWalletReceivingAddress(walletId))),
); );
showFloatingFlushBar( showFloatingFlushBar(
type: FlushBarType.info, type: FlushBarType.info,
@ -276,7 +524,8 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
receivingAddress, ref.watch(
pWalletReceivingAddress(walletId)),
style: STextStyles.itemSubtitle12(context), style: STextStyles.itemSubtitle12(context),
), ),
), ),
@ -286,25 +535,22 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
), ),
), ),
), ),
if (coin != Coin.epicCash && ),
coin != Coin.ethereum && if (ref.watch(pWallets
coin != Coin.banano && .select((value) => value.getWallet(walletId)))
coin != Coin.nano && is MultiAddressInterface ||
coin != Coin.stellar && supportsSpark)
coin != Coin.stellarTestnet &&
coin != Coin.tezos)
const SizedBox( const SizedBox(
height: 12, height: 12,
), ),
if (coin != Coin.epicCash && if (ref.watch(pWallets
coin != Coin.ethereum && .select((value) => value.getWallet(walletId)))
coin != Coin.banano && is MultiAddressInterface ||
coin != Coin.nano && supportsSpark)
coin != Coin.stellar &&
coin != Coin.stellarTestnet &&
coin != Coin.tezos)
TextButton( TextButton(
onPressed: generateNewAddress, onPressed: supportsSpark && _showSparkAddress
? generateNewSparkAddress
: generateNewAddress,
style: Theme.of(context) style: Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!
.getSecondaryEnabledButtonStyle(context), .getSecondaryEnabledButtonStyle(context),
@ -328,7 +574,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
QrImageView( QrImageView(
data: AddressUtils.buildUriString( data: AddressUtils.buildUriString(
coin, coin,
receivingAddress, _qrcodeContent ?? "",
{}, {},
), ),
size: MediaQuery.of(context).size.width / 2, size: MediaQuery.of(context).size.width / 2,
@ -347,7 +593,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
RouteGenerator.useMaterialPageRoute, RouteGenerator.useMaterialPageRoute,
builder: (_) => GenerateUriQrCodeView( builder: (_) => GenerateUriQrCodeView(
coin: coin, coin: coin,
receivingAddress: receivingAddress, receivingAddress: _qrcodeContent ?? "",
), ),
settings: const RouteSettings( settings: const RouteSettings(
name: GenerateUriQrCodeView.routeName, name: GenerateUriQrCodeView.routeName,

View file

@ -120,7 +120,7 @@ class _ConfirmTransactionViewState
), ),
); );
late String txid; final List<String> txids = [];
Future<TxData> txDataFuture; Future<TxData> txDataFuture;
final note = noteController.text; final note = noteController.text;
@ -140,10 +140,25 @@ class _ConfirmTransactionViewState
} else if (widget.isPaynymTransaction) { } else if (widget.isPaynymTransaction) {
txDataFuture = wallet.confirmSend(txData: widget.txData); txDataFuture = wallet.confirmSend(txData: widget.txData);
} else { } else {
if (wallet is FiroWallet && if (wallet is FiroWallet) {
ref.read(publicPrivateBalanceStateProvider.state).state == switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
"Private") { 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); txDataFuture = wallet.confirmSendLelantus(txData: widget.txData);
break;
case FiroType.spark:
txDataFuture = wallet.confirmSendSpark(txData: widget.txData);
break;
}
} else { } else {
if (coin == Coin.epicCash) { if (coin == Coin.epicCash) {
txDataFuture = wallet.confirmSend( txDataFuture = wallet.confirmSend(
@ -165,10 +180,16 @@ class _ConfirmTransactionViewState
sendProgressController.triggerSuccess?.call(); sendProgressController.triggerSuccess?.call();
await Future<void>.delayed(const Duration(seconds: 5)); 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); ref.refresh(desktopUseUTXOs);
// save note // save note
for (final txid in txids) {
await ref.read(mainDBProvider).putTransactionNote( await ref.read(mainDBProvider).putTransactionNote(
TransactionNote( TransactionNote(
walletId: walletId, walletId: walletId,
@ -176,6 +197,7 @@ class _ConfirmTransactionViewState
value: note, value: note,
), ),
); );
}
if (widget.isTokenTx) { if (widget.isTokenTx) {
unawaited(ref.read(tokenServiceProvider)!.refresh()); unawaited(ref.read(tokenServiceProvider)!.refresh());
@ -233,10 +255,14 @@ class _ConfirmTransactionViewState
const SizedBox( const SizedBox(
height: 24, height: 24,
), ),
Text( Flexible(
child: SingleChildScrollView(
child: SelectableText(
e.toString(), e.toString(),
style: STextStyles.smallMed14(context), style: STextStyles.smallMed14(context),
), ),
),
),
const SizedBox( const SizedBox(
height: 56, height: 56,
), ),
@ -319,6 +345,48 @@ class _ConfirmTransactionViewState
} else { } else {
unit = coin.ticker; 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( return ConditionalParent(
condition: !isDesktop, condition: !isDesktop,
builder: (child) => Background( builder: (child) => Background(
@ -424,7 +492,8 @@ class _ConfirmTransactionViewState
Text( Text(
widget.isPaynymTransaction widget.isPaynymTransaction
? widget.txData.paynymAccountLite!.nymName ? widget.txData.paynymAccountLite!.nymName
: widget.txData.recipients!.first.address, : widget.txData.recipients?.first.address ??
widget.txData.sparkRecipients!.first.address,
style: STextStyles.itemSubtitle12(context), style: STextStyles.itemSubtitle12(context),
), ),
], ],
@ -443,7 +512,7 @@ class _ConfirmTransactionViewState
), ),
SelectableText( SelectableText(
ref.watch(pAmountFormatter(coin)).format( ref.watch(pAmountFormatter(coin)).format(
widget.txData.amount!, amount,
ethContract: ref ethContract: ref
.watch(tokenServiceProvider) .watch(tokenServiceProvider)
?.tokenContract, ?.tokenContract,
@ -468,9 +537,7 @@ class _ConfirmTransactionViewState
style: STextStyles.smallMed12(context), style: STextStyles.smallMed12(context),
), ),
SelectableText( SelectableText(
ref ref.watch(pAmountFormatter(coin)).format(fee!),
.watch(pAmountFormatter(coin))
.format(widget.txData.fee!),
style: STextStyles.itemSubtitle12(context), style: STextStyles.itemSubtitle12(context),
textAlign: TextAlign.right, textAlign: TextAlign.right,
), ),
@ -494,7 +561,7 @@ class _ConfirmTransactionViewState
height: 4, height: 4,
), ),
SelectableText( SelectableText(
"~${widget.txData.fee!.raw.toInt() ~/ widget.txData.vSize!}", "~${fee!.raw.toInt() ~/ widget.txData.vSize!}",
style: STextStyles.itemSubtitle12(context), style: STextStyles.itemSubtitle12(context),
), ),
], ],
@ -625,7 +692,6 @@ class _ConfirmTransactionViewState
), ),
Builder( Builder(
builder: (context) { builder: (context) {
final amount = widget.txData.amount!;
final externalCalls = ref.watch( final externalCalls = ref.watch(
prefsChangeNotifierProvider.select( prefsChangeNotifierProvider.select(
(value) => value.externalCalls)); (value) => value.externalCalls));
@ -723,9 +789,12 @@ class _ConfirmTransactionViewState
height: 2, height: 2,
), ),
SelectableText( SelectableText(
// TODO: [prio=high] spark transaction specifics - better handling
widget.isPaynymTransaction widget.isPaynymTransaction
? widget.txData.paynymAccountLite!.nymName ? widget.txData.paynymAccountLite!.nymName
: widget.txData.recipients!.first.address, : widget.txData.recipients?.first.address ??
widget.txData.sparkRecipients!.first
.address,
style: STextStyles.desktopTextExtraExtraSmall( style: STextStyles.desktopTextExtraExtraSmall(
context) context)
.copyWith( .copyWith(
@ -759,24 +828,15 @@ class _ConfirmTransactionViewState
const SizedBox( const SizedBox(
height: 2, height: 2,
), ),
Builder( SelectableText(
builder: (context) { ref.watch(pAmountFormatter(coin)).format(fee!),
final fee = widget.txData.fee!; style: STextStyles.desktopTextExtraExtraSmall(
return SelectableText(
ref
.watch(pAmountFormatter(coin))
.format(fee),
style:
STextStyles.desktopTextExtraExtraSmall(
context) context)
.copyWith( .copyWith(
color: Theme.of(context) color: Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!
.textDark, .textDark,
), ),
);
},
), ),
], ],
), ),
@ -981,15 +1041,9 @@ class _ConfirmTransactionViewState
color: Theme.of(context) color: Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!
.textFieldDefaultBG, .textFieldDefaultBG,
child: Builder( child: SelectableText(
builder: (context) { ref.watch(pAmountFormatter(coin)).format(fee!),
final fee = widget.txData.fee!;
return SelectableText(
ref.watch(pAmountFormatter(coin)).format(fee),
style: STextStyles.itemSubtitle(context), style: STextStyles.itemSubtitle(context),
);
},
), ),
), ),
), ),
@ -1025,7 +1079,7 @@ class _ConfirmTransactionViewState
.extension<StackColors>()! .extension<StackColors>()!
.textFieldDefaultBG, .textFieldDefaultBG,
child: SelectableText( child: SelectableText(
"~${widget.txData.fee!.raw.toInt() ~/ widget.txData.vSize!}", "~${fee!.raw.toInt() ~/ widget.txData.vSize!}",
style: STextStyles.itemSubtitle(context), style: STextStyles.itemSubtitle(context),
), ),
), ),
@ -1069,14 +1123,8 @@ class _ConfirmTransactionViewState
.textConfirmTotalAmount, .textConfirmTotalAmount,
), ),
), ),
Builder(builder: (context) { SelectableText(
final fee = widget.txData.fee!; ref.watch(pAmountFormatter(coin)).format(amount + fee!),
final amount = widget.txData.amount!;
return SelectableText(
ref
.watch(pAmountFormatter(coin))
.format(amount + fee),
style: isDesktop style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context) ? STextStyles.desktopTextExtraExtraSmall(context)
.copyWith( .copyWith(
@ -1090,8 +1138,7 @@ class _ConfirmTransactionViewState
.textConfirmTotalAmount, .textConfirmTotalAmount,
), ),
textAlign: TextAlign.right, textAlign: TextAlign.right,
); ),
}),
], ],
), ),
), ),

File diff suppressed because it is too large Load diff

View file

@ -101,9 +101,9 @@ class _FiroBalanceSelectionSheetState
onTap: () { onTap: () {
final state = final state =
ref.read(publicPrivateBalanceStateProvider.state).state; ref.read(publicPrivateBalanceStateProvider.state).state;
if (state != "Private") { if (state != FiroType.spark) {
ref.read(publicPrivateBalanceStateProvider.state).state = ref.read(publicPrivateBalanceStateProvider.state).state =
"Private"; FiroType.spark;
} }
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@ -122,7 +122,7 @@ class _FiroBalanceSelectionSheetState
activeColor: Theme.of(context) activeColor: Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!
.radioButtonIconEnabled, .radioButtonIconEnabled,
value: "Private", value: FiroType.spark,
groupValue: ref groupValue: ref
.watch( .watch(
publicPrivateBalanceStateProvider.state) publicPrivateBalanceStateProvider.state)
@ -131,7 +131,7 @@ class _FiroBalanceSelectionSheetState
ref ref
.read(publicPrivateBalanceStateProvider .read(publicPrivateBalanceStateProvider
.state) .state)
.state = "Private"; .state = FiroType.spark;
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@ -149,7 +149,86 @@ class _FiroBalanceSelectionSheetState
// Row( // Row(
// children: [ // children: [
Text( 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), style: STextStyles.titleBold12(context),
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
@ -180,9 +259,9 @@ class _FiroBalanceSelectionSheetState
onTap: () { onTap: () {
final state = final state =
ref.read(publicPrivateBalanceStateProvider.state).state; ref.read(publicPrivateBalanceStateProvider.state).state;
if (state != "Public") { if (state != FiroType.public) {
ref.read(publicPrivateBalanceStateProvider.state).state = ref.read(publicPrivateBalanceStateProvider.state).state =
"Public"; FiroType.public;
} }
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@ -200,7 +279,7 @@ class _FiroBalanceSelectionSheetState
activeColor: Theme.of(context) activeColor: Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!
.radioButtonIconEnabled, .radioButtonIconEnabled,
value: "Public", value: FiroType.public,
groupValue: ref groupValue: ref
.watch( .watch(
publicPrivateBalanceStateProvider.state) publicPrivateBalanceStateProvider.state)
@ -209,7 +288,7 @@ class _FiroBalanceSelectionSheetState
ref ref
.read(publicPrivateBalanceStateProvider .read(publicPrivateBalanceStateProvider
.state) .state)
.state = "Public"; .state = FiroType.public;
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),

View file

@ -348,7 +348,7 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
.getWallet(walletId) .getWallet(walletId)
.cryptoCurrency .cryptoCurrency
.validateAddress(address ?? ""); .validateAddress(address ?? "");
ref.read(previewTxButtonStateProvider.state).state = ref.read(previewTokenTxButtonStateProvider.state).state =
(isValidAddress && amount != null && amount > Amount.zero); (isValidAddress && amount != null && amount > Amount.zero);
} }
@ -1227,12 +1227,14 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
), ),
TextButton( TextButton(
onPressed: ref onPressed: ref
.watch(previewTxButtonStateProvider.state) .watch(
previewTokenTxButtonStateProvider.state)
.state .state
? _previewTransaction ? _previewTransaction
: null, : null,
style: ref style: ref
.watch(previewTxButtonStateProvider.state) .watch(
previewTokenTxButtonStateProvider.state)
.state .state
? Theme.of(context) ? Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!

View file

@ -15,14 +15,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:stackwallet/db/hive/db.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/notifications/show_flush_bar.dart';
import 'package:stackwallet/providers/global/debug_service_provider.dart'; import 'package:stackwallet/providers/global/debug_service_provider.dart';
import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.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/text_styles.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/background.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( const SizedBox(
height: 12, height: 12,
), ),
@ -345,9 +252,6 @@ class HiddenSettings extends StatelessWidget {
} }
}, },
), ),
const SizedBox(
height: 12,
),
Consumer( Consumer(
builder: (_, ref, __) { builder: (_, ref, __) {
return GestureDetector( 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),
// ),
// ),
// ),
], ],
), ),
), ),

View file

@ -40,13 +40,16 @@ class TxIcon extends ConsumerWidget {
bool isReceived, bool isReceived,
bool isPending, bool isPending,
TransactionSubType subType, TransactionSubType subType,
TransactionType type,
IThemeAssets assets, IThemeAssets assets,
) { ) {
if (subType == TransactionSubType.cashFusion) { if (subType == TransactionSubType.cashFusion) {
return Assets.svg.txCashFusion; return Assets.svg.txCashFusion;
} }
if (!isReceived && subType == TransactionSubType.mint) { if ((!isReceived && subType == TransactionSubType.mint) ||
(subType == TransactionSubType.sparkMint &&
type == TransactionType.sentToSelf)) {
if (isCancelled) { if (isCancelled) {
return Assets.svg.anonymizeFailed; return Assets.svg.anonymizeFailed;
} }
@ -91,6 +94,7 @@ class TxIcon extends ConsumerWidget {
ref.watch(pWallets).getWallet(tx.walletId).cryptoCurrency.minConfirms, ref.watch(pWallets).getWallet(tx.walletId).cryptoCurrency.minConfirms,
), ),
tx.subType, tx.subType,
tx.type,
ref.watch(themeAssetsProvider), ref.watch(themeAssetsProvider),
); );
} else if (transaction is TransactionV2) { } else if (transaction is TransactionV2) {
@ -104,6 +108,7 @@ class TxIcon extends ConsumerWidget {
ref.watch(pWallets).getWallet(tx.walletId).cryptoCurrency.minConfirms, ref.watch(pWallets).getWallet(tx.walletId).cryptoCurrency.minConfirms,
), ),
tx.subType, tx.subType,
tx.type,
ref.watch(themeAssetsProvider), ref.watch(themeAssetsProvider),
); );
} else { } else {

View file

@ -25,8 +25,10 @@ import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
enum _BalanceType { enum _BalanceType {
available, available,
full, full,
privateAvailable, lelantusAvailable,
privateFull; lelantusFull,
sparkAvailable,
sparkFull;
} }
class WalletBalanceToggleSheet extends ConsumerWidget { class WalletBalanceToggleSheet extends ConsumerWidget {
@ -39,9 +41,10 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { 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 coin = ref.watch(pWalletCoin(walletId));
final isFiro = coin == Coin.firo || coin == Coin.firoTestNet;
Balance balance = ref.watch(pWalletBalance(walletId)); Balance balance = ref.watch(pWalletBalance(walletId));
@ -52,18 +55,27 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
: _BalanceType.full; : _BalanceType.full;
Balance? balanceSecondary; Balance? balanceSecondary;
if (coin == Coin.firo || coin == Coin.firoTestNet) { Balance? balanceTertiary;
if (isFiro) {
balanceSecondary = ref.watch(pWalletBalanceSecondary(walletId)); balanceSecondary = ref.watch(pWalletBalanceSecondary(walletId));
balanceTertiary = ref.watch(pWalletBalanceTertiary(walletId));
final temp = balance; switch (ref.watch(publicPrivateBalanceStateProvider.state).state) {
balance = balanceSecondary!; case FiroType.spark:
balanceSecondary = temp;
if (ref.watch(publicPrivateBalanceStateProvider.state).state ==
"Private") {
_bal = _bal == _BalanceType.available _bal = _bal == _BalanceType.available
? _BalanceType.privateAvailable ? _BalanceType.sparkAvailable
: _BalanceType.privateFull; : _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, height: 24,
), ),
BalanceSelector( BalanceSelector(
title: title: "Available${isFiro ? " public" : ""} balance",
"Available${balanceSecondary != null ? " public" : ""} balance",
coin: coin, coin: coin,
balance: balance.spendable, balance: balance.spendable,
onPressed: () { onPressed: () {
ref.read(walletBalanceToggleStateProvider.state).state = ref.read(walletBalanceToggleStateProvider.state).state =
WalletBalanceToggleState.available; WalletBalanceToggleState.available;
ref.read(publicPrivateBalanceStateProvider.state).state = ref.read(publicPrivateBalanceStateProvider.state).state =
"Public"; FiroType.public;
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
onChanged: (_) { onChanged: (_) {
ref.read(walletBalanceToggleStateProvider.state).state = ref.read(walletBalanceToggleStateProvider.state).state =
WalletBalanceToggleState.available; WalletBalanceToggleState.available;
ref.read(publicPrivateBalanceStateProvider.state).state = ref.read(publicPrivateBalanceStateProvider.state).state =
"Public"; FiroType.public;
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
value: _BalanceType.available, value: _BalanceType.available,
@ -141,22 +152,21 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
height: 12, height: 12,
), ),
BalanceSelector( BalanceSelector(
title: title: "Full${isFiro ? " public" : ""} balance",
"Full${balanceSecondary != null ? " public" : ""} balance",
coin: coin, coin: coin,
balance: balance.total, balance: balance.total,
onPressed: () { onPressed: () {
ref.read(walletBalanceToggleStateProvider.state).state = ref.read(walletBalanceToggleStateProvider.state).state =
WalletBalanceToggleState.full; WalletBalanceToggleState.full;
ref.read(publicPrivateBalanceStateProvider.state).state = ref.read(publicPrivateBalanceStateProvider.state).state =
"Public"; FiroType.public;
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
onChanged: (_) { onChanged: (_) {
ref.read(walletBalanceToggleStateProvider.state).state = ref.read(walletBalanceToggleStateProvider.state).state =
WalletBalanceToggleState.full; WalletBalanceToggleState.full;
ref.read(publicPrivateBalanceStateProvider.state).state = ref.read(publicPrivateBalanceStateProvider.state).state =
"Public"; FiroType.public;
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
value: _BalanceType.full, value: _BalanceType.full,
@ -168,24 +178,24 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
), ),
if (balanceSecondary != null) if (balanceSecondary != null)
BalanceSelector( BalanceSelector(
title: "Available private balance", title: "Available lelantus balance",
coin: coin, coin: coin,
balance: balanceSecondary.spendable, balance: balanceSecondary.spendable,
onPressed: () { onPressed: () {
ref.read(walletBalanceToggleStateProvider.state).state = ref.read(walletBalanceToggleStateProvider.state).state =
WalletBalanceToggleState.available; WalletBalanceToggleState.available;
ref.read(publicPrivateBalanceStateProvider.state).state = ref.read(publicPrivateBalanceStateProvider.state).state =
"Private"; FiroType.lelantus;
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
onChanged: (_) { onChanged: (_) {
ref.read(walletBalanceToggleStateProvider.state).state = ref.read(walletBalanceToggleStateProvider.state).state =
WalletBalanceToggleState.available; WalletBalanceToggleState.available;
ref.read(publicPrivateBalanceStateProvider.state).state = ref.read(publicPrivateBalanceStateProvider.state).state =
"Private"; FiroType.lelantus;
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
value: _BalanceType.privateAvailable, value: _BalanceType.lelantusAvailable,
groupValue: _bal, groupValue: _bal,
), ),
if (balanceSecondary != null) if (balanceSecondary != null)
@ -194,24 +204,76 @@ class WalletBalanceToggleSheet extends ConsumerWidget {
), ),
if (balanceSecondary != null) if (balanceSecondary != null)
BalanceSelector( BalanceSelector(
title: "Full private balance", title: "Full lelantus balance",
coin: coin, coin: coin,
balance: balanceSecondary.total, balance: balanceSecondary.total,
onPressed: () { onPressed: () {
ref.read(walletBalanceToggleStateProvider.state).state = ref.read(walletBalanceToggleStateProvider.state).state =
WalletBalanceToggleState.full; WalletBalanceToggleState.full;
ref.read(publicPrivateBalanceStateProvider.state).state = ref.read(publicPrivateBalanceStateProvider.state).state =
"Private"; FiroType.lelantus;
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
onChanged: (_) { onChanged: (_) {
ref.read(walletBalanceToggleStateProvider.state).state = ref.read(walletBalanceToggleStateProvider.state).state =
WalletBalanceToggleState.full; WalletBalanceToggleState.full;
ref.read(publicPrivateBalanceStateProvider.state).state = ref.read(publicPrivateBalanceStateProvider.state).state =
"Private"; FiroType.lelantus;
Navigator.of(context).pop(); 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, groupValue: _bal,
), ),
const SizedBox( const SizedBox(

View file

@ -12,6 +12,7 @@ import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_native_splash/cli_commands.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.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/enums/wallet_balance_toggle_state.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.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'; import 'package:stackwallet/widgets/conditional_parent.dart';
class WalletSummaryInfo extends ConsumerWidget { class WalletSummaryInfo extends ConsumerWidget {
@ -45,6 +47,8 @@ class WalletSummaryInfo extends ConsumerWidget {
showModalBottomSheet<dynamic>( showModalBottomSheet<dynamic>(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
context: context, context: context,
useSafeArea: true,
isScrollControlled: true,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical( borderRadius: BorderRadius.vertical(
top: Radius.circular(20), top: Radius.circular(20),
@ -58,10 +62,6 @@ class WalletSummaryInfo extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
debugPrint("BUILD: $runtimeType"); debugPrint("BUILD: $runtimeType");
bool isMonkey = true;
final receivingAddress = ref.watch(pWalletReceivingAddress(walletId));
final externalCalls = ref.watch( final externalCalls = ref.watch(
prefsChangeNotifierProvider.select((value) => value.externalCalls)); prefsChangeNotifierProvider.select((value) => value.externalCalls));
final coin = ref.watch(pWalletCoin(walletId)); final coin = ref.watch(pWalletCoin(walletId));
@ -81,19 +81,28 @@ class WalletSummaryInfo extends ConsumerWidget {
WalletBalanceToggleState.available; WalletBalanceToggleState.available;
final Amount balanceToShow; final Amount balanceToShow;
String title; final String title;
if (coin == Coin.firo || coin == Coin.firoTestNet) { if (coin == Coin.firo || coin == Coin.firoTestNet) {
final _showPrivate = final type = ref.watch(publicPrivateBalanceStateProvider.state).state;
ref.watch(publicPrivateBalanceStateProvider.state).state == "Private"; 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; case FiroType.public:
final balance = ref.watch(pWalletBalance(walletId));
balanceToShow = _showAvailable ? bal.spendable : bal.total; balanceToShow = _showAvailable ? balance.spendable : balance.total;
title = _showAvailable ? "Available" : "Full"; break;
title += _showPrivate ? " private balance" : " public balance"; }
} else { } else {
balanceToShow = _showAvailable ? balance.spendable : balance.total; balanceToShow = _showAvailable ? balance.spendable : balance.total;
title = _showAvailable ? "Available balance" : "Full balance"; title = _showAvailable ? "Available balance" : "Full balance";
@ -102,8 +111,8 @@ class WalletSummaryInfo extends ConsumerWidget {
List<int>? imageBytes; List<int>? imageBytes;
if (coin == Coin.banano) { if (coin == Coin.banano) {
// TODO: [prio=high] fix this and uncomment: imageBytes = (ref.watch(pWallets).getWallet(walletId) as BananoWallet)
// imageBytes = (manager.wallet as BananoWallet).getMonkeyImageBytes(); .getMonkeyImageBytes();
} }
return ConditionalParent( return ConditionalParent(

View file

@ -31,7 +31,6 @@ import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart'; import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.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/format.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';
@ -843,34 +842,10 @@ class _DesktopTransactionCardRowState
late final String walletId; late final String walletId;
late final int minConfirms; late final int minConfirms;
String whatIsIt(TransactionType type, Coin coin, int height) { String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel(
if (_transaction.subType == TransactionSubType.mint || currentChainHeight: height,
_transaction.subType == TransactionSubType.cashFusion) { minConfirms: minConfirms,
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;
}
}
@override @override
void initState() { void initState() {
@ -917,7 +892,7 @@ class _DesktopTransactionCardRowState
final Amount amount; final Amount amount;
if (_transaction.subType == TransactionSubType.cashFusion) { if (_transaction.subType == TransactionSubType.cashFusion) {
amount = _transaction.getAmountReceivedThisWallet(coin: coin); amount = _transaction.getAmountReceivedInThisWallet(coin: coin);
} else { } else {
switch (_transaction.type) { switch (_transaction.type) {
case TransactionType.outgoing: case TransactionType.outgoing:
@ -926,7 +901,11 @@ class _DesktopTransactionCardRowState
case TransactionType.incoming: case TransactionType.incoming:
case TransactionType.sentToSelf: 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; break;
case TransactionType.unknown: case TransactionType.unknown:
@ -994,8 +973,7 @@ class _DesktopTransactionCardRowState
flex: 3, flex: 3,
child: Text( child: Text(
whatIsIt( whatIsIt(
_transaction.type, _transaction,
coin,
currentHeight, currentHeight,
), ),
style: style:

View file

@ -44,42 +44,13 @@ class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
String whatIsIt( String whatIsIt(
Coin coin, Coin coin,
int currentHeight, int currentHeight,
) { ) =>
final confirmedStatus = _transaction.isConfirmed( _transaction.statusLabel(
currentHeight, currentChainHeight: currentHeight,
minConfirms:
ref.read(pWallets).getWallet(walletId).cryptoCurrency.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 @override
void initState() { void initState() {
_transaction = widget.transaction; _transaction = widget.transaction;
@ -121,7 +92,7 @@ class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
final Amount amount; final Amount amount;
if (_transaction.subType == TransactionSubType.cashFusion) { if (_transaction.subType == TransactionSubType.cashFusion) {
amount = _transaction.getAmountReceivedThisWallet(coin: coin); amount = _transaction.getAmountReceivedInThisWallet(coin: coin);
} else { } else {
switch (_transaction.type) { switch (_transaction.type) {
case TransactionType.outgoing: case TransactionType.outgoing:
@ -130,7 +101,11 @@ class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
case TransactionType.incoming: case TransactionType.incoming:
case TransactionType.sentToSelf: 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; break;
case TransactionType.unknown: case TransactionType.unknown:

View file

@ -95,7 +95,12 @@ class _TransactionV2DetailsViewState
minConfirms = minConfirms =
ref.read(pWallets).getWallet(walletId).cryptoCurrency.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); fee = _transaction.getFee(coin: coin);
}
if (_transaction.subType == TransactionSubType.cashFusion || if (_transaction.subType == TransactionSubType.cashFusion ||
_transaction.type == TransactionType.sentToSelf) { _transaction.type == TransactionType.sentToSelf) {
@ -107,7 +112,7 @@ class _TransactionV2DetailsViewState
unit = coin.ticker; unit = coin.ticker;
if (_transaction.subType == TransactionSubType.cashFusion) { if (_transaction.subType == TransactionSubType.cashFusion) {
amount = _transaction.getAmountReceivedThisWallet(coin: coin); amount = _transaction.getAmountReceivedInThisWallet(coin: coin);
data = _transaction.outputs data = _transaction.outputs
.where((e) => e.walletOwns) .where((e) => e.walletOwns)
.map((e) => ( .map((e) => (
@ -131,7 +136,11 @@ class _TransactionV2DetailsViewState
case TransactionType.incoming: case TransactionType.incoming:
case TransactionType.sentToSelf: 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 data = _transaction.outputs
.where((e) => e.walletOwns) .where((e) => e.walletOwns)
.map((e) => ( .map((e) => (
@ -164,77 +173,10 @@ class _TransactionV2DetailsViewState
super.dispose(); super.dispose();
} }
String whatIsIt(TransactionV2 tx, int height) { String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel(
final type = tx.type; currentChainHeight: height,
if (coin == Coin.firo || coin == Coin.firoTestNet) { minConfirms: minConfirms,
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;
}
}
Future<String> fetchContactNameFor(String address) async { Future<String> fetchContactNameFor(String address) async {
if (address.isEmpty) { if (address.isEmpty) {

View file

@ -8,6 +8,8 @@
* *
*/ */
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
@ -40,6 +42,9 @@ class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
bool _hasLoaded = false; bool _hasLoaded = false;
List<TransactionV2> _transactions = []; List<TransactionV2> _transactions = [];
late final StreamSubscription<List<TransactionV2>> _subscription;
late final QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> _query;
BorderRadius get _borderRadiusFirst { BorderRadius get _borderRadiusFirst {
return BorderRadius.only( return BorderRadius.only(
topLeft: Radius.circular( topLeft: Radius.circular(
@ -63,18 +68,38 @@ class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
} }
@override @override
Widget build(BuildContext context) { void initState() {
final coin = ref.watch(pWallets).getWallet(widget.walletId).info.coin; _query = ref
.read(mainDBProvider)
return FutureBuilder(
future: ref
.watch(mainDBProvider)
.isar .isar
.transactionV2s .transactionV2s
.where() .where()
.walletIdEqualTo(widget.walletId) .walletIdEqualTo(widget.walletId)
.sortByTimestampDesc() .sortByTimestampDesc();
.findAll(),
_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) { builder: (fbContext, AsyncSnapshot<List<TransactionV2>> snapshot) {
if (snapshot.connectionState == ConnectionState.done && if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) { snapshot.hasData) {

View file

@ -46,8 +46,6 @@ import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart'; import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
import 'package:stackwallet/providers/ui/unread_notifications_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/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/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/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.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/constants.dart';
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
import 'package:stackwallet/utilities/enums/coin_enum.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/logger.dart';
import 'package:stackwallet/utilities/show_loading.dart'; import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/text_styles.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/impl/firo_wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.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/paynym_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/conditional_parent.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/app_bar_icon_button.dart';
@ -118,6 +116,8 @@ class _WalletViewState extends ConsumerState<WalletView> {
late final String walletId; late final String walletId;
late final Coin coin; late final Coin coin;
late final bool isSparkWallet;
late final bool _shouldDisableAutoSyncOnLogOut; late final bool _shouldDisableAutoSyncOnLogOut;
late WalletSyncStatus _currentSyncStatus; late WalletSyncStatus _currentSyncStatus;
@ -174,6 +174,8 @@ class _WalletViewState extends ConsumerState<WalletView> {
_shouldDisableAutoSyncOnLogOut = false; _shouldDisableAutoSyncOnLogOut = false;
} }
isSparkWallet = wallet is SparkInterface;
if (coin == Coin.firo && if (coin == Coin.firo &&
(wallet as FiroWallet).lelantusCoinIsarRescanRequired) { (wallet as FiroWallet).lelantusCoinIsarRescanRequired) {
_rescanningOnOpen = true; _rescanningOnOpen = true;
@ -433,7 +435,8 @@ class _WalletViewState extends ConsumerState<WalletView> {
} }
try { try {
await firoWallet.anonymizeAllPublicFunds(); // await firoWallet.anonymizeAllLelantus();
await firoWallet.anonymizeAllSpark();
shouldPop = true; shouldPop = true;
if (mounted) { if (mounted) {
Navigator.of(context).popUntil( Navigator.of(context).popUntil(
@ -760,11 +763,11 @@ class _WalletViewState extends ConsumerState<WalletView> {
), ),
), ),
), ),
if (coin == Coin.firo) if (isSparkWallet)
const SizedBox( const SizedBox(
height: 10, height: 10,
), ),
if (coin == Coin.firo) if (isSparkWallet)
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row( child: Row(
@ -951,20 +954,21 @@ class _WalletViewState extends ConsumerState<WalletView> {
label: "Send", label: "Send",
icon: const SendNavIcon(), icon: const SendNavIcon(),
onTap: () { onTap: () {
switch (ref // not sure what this is supposed to accomplish?
.read(walletBalanceToggleStateProvider.state) // switch (ref
.state) { // .read(walletBalanceToggleStateProvider.state)
case WalletBalanceToggleState.full: // .state) {
ref // case WalletBalanceToggleState.full:
.read(publicPrivateBalanceStateProvider.state) // ref
.state = "Public"; // .read(publicPrivateBalanceStateProvider.state)
break; // .state = "Public";
case WalletBalanceToggleState.available: // break;
ref // case WalletBalanceToggleState.available:
.read(publicPrivateBalanceStateProvider.state) // ref
.state = "Private"; // .read(publicPrivateBalanceStateProvider.state)
break; // .state = "Private";
} // break;
// }
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
SendView.routeName, SendView.routeName,
arguments: Tuple2( arguments: Tuple2(

View file

@ -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/pages/wallets_view/sub_widgets/favorite_wallets.dart';
import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/themes/theme_providers.dart';
import 'package:stackwallet/wallets/isar/providers/all_wallets_info_provider.dart';
class WalletsView extends ConsumerWidget { class WalletsView extends ConsumerWidget {
const WalletsView({Key? key}) : super(key: key); const WalletsView({Key? key}) : super(key: key);
@ -25,7 +26,7 @@ class WalletsView extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
debugPrint("BUILD: $runtimeType"); debugPrint("BUILD: $runtimeType");
final hasWallets = ref.watch(pWallets).hasWallets; final hasWallets = ref.watch(pAllWalletsInfo).isNotEmpty;
final showFavorites = ref.watch(prefsChangeNotifierProvider final showFavorites = ref.watch(prefsChangeNotifierProvider
.select((value) => value.showFavoriteWallets)); .select((value) => value.showFavoriteWallets));

View file

@ -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/settings_views/global_settings_view/hidden_settings.dart';
import 'package:stackwallet/pages/wallets_view/sub_widgets/empty_wallets.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/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/themes/theme_providers.dart';
import 'package:stackwallet/utilities/text_styles.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/animated_widgets/rotate_icon.dart';
import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
@ -36,7 +36,7 @@ class _MyStackViewState extends ConsumerState<MyStackView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType"); debugPrint("BUILD: $runtimeType");
final hasWallets = ref.watch(pWallets).hasWallets; final hasWallets = ref.watch(pAllWalletsInfo).isNotEmpty;
return Background( return Background(
child: Column( child: Column(

View file

@ -10,6 +10,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/providers/wallet/wallet_balance_toggle_state_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
@ -80,6 +81,8 @@ class DesktopPrivateBalanceToggleButton extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final currentType = ref.watch(publicPrivateBalanceStateProvider);
return SizedBox( return SizedBox(
height: 22, height: 22,
width: 22, width: 22,
@ -87,13 +90,21 @@ class DesktopPrivateBalanceToggleButton extends ConsumerWidget {
color: Theme.of(context).extension<StackColors>()!.buttonBackSecondary, color: Theme.of(context).extension<StackColors>()!.buttonBackSecondary,
splashColor: Theme.of(context).extension<StackColors>()!.highlight, splashColor: Theme.of(context).extension<StackColors>()!.highlight,
onPressed: () { onPressed: () {
if (ref.read(walletPrivateBalanceToggleStateProvider.state).state == switch (currentType) {
WalletBalanceToggleState.available) { case FiroType.public:
ref.read(walletPrivateBalanceToggleStateProvider.state).state = ref.read(publicPrivateBalanceStateProvider.state).state =
WalletBalanceToggleState.full; FiroType.lelantus;
} else { break;
ref.read(walletPrivateBalanceToggleStateProvider.state).state =
WalletBalanceToggleState.available; case FiroType.lelantus:
ref.read(publicPrivateBalanceStateProvider.state).state =
FiroType.spark;
break;
case FiroType.spark:
ref.read(publicPrivateBalanceStateProvider.state).state =
FiroType.public;
break;
} }
onPressed?.call(); onPressed?.call();
}, },
@ -110,12 +121,14 @@ class DesktopPrivateBalanceToggleButton extends ConsumerWidget {
child: Center( child: Center(
child: Image( child: Image(
image: AssetImage( image: AssetImage(
ref.watch(walletPrivateBalanceToggleStateProvider.state).state == currentType == FiroType.public
WalletBalanceToggleState.available ? Assets.png.glasses
? Assets.png.glassesHidden : Assets.png.glassesHidden,
: Assets.png.glasses,
), ),
width: 16, width: 16,
color: currentType == FiroType.spark
? Theme.of(context).extension<StackColors>()!.accentColorYellow
: null,
), ),
), ),
), ),

View file

@ -10,14 +10,18 @@
import 'dart:async'; import 'dart:async';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:isar/isar.dart';
import 'package:qr_flutter/qr_flutter.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/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.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/pages/token_view/token_view.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/themes/stack_colors.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/text_styles.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.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_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
@ -57,10 +63,15 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
late final Coin coin; late final Coin coin;
late final String walletId; late final String walletId;
late final ClipboardInterface clipboard; late final ClipboardInterface clipboard;
late final bool supportsSpark;
String? _sparkAddress;
String? _qrcodeContent;
bool _showSparkAddress = true;
Future<void> generateNewAddress() async { Future<void> generateNewAddress() async {
final wallet = ref.read(pWallets).getWallet(walletId); final wallet = ref.read(pWallets).getWallet(walletId);
if (wallet is Bip39HDWallet) { if (wallet is MultiAddressInterface) {
bool shouldPop = false; bool shouldPop = false;
unawaited( unawaited(
showDialog( 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 @override
void initState() { void initState() {
walletId = widget.walletId; walletId = widget.walletId;
coin = ref.read(pWalletInfo(walletId)).coin; coin = ref.read(pWalletInfo(walletId)).coin;
clipboard = widget.clipboard; 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(); super.initState();
} }
@override
void dispose() {
_streamSub?.cancel();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType"); 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( return Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ 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( MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
clipboard.setData( 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( showFloatingFlushBar(
type: FlushBarType.info, type: FlushBarType.info,
@ -179,9 +433,9 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
receivingAddress, ref.watch(pWalletReceivingAddress(walletId)),
style: style: STextStyles.desktopTextExtraExtraSmall(
STextStyles.desktopTextExtraExtraSmall(context) context)
.copyWith( .copyWith(
color: Theme.of(context) color: Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!
@ -197,26 +451,23 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
), ),
), ),
), ),
if (coin != Coin.epicCash && ),
coin != Coin.ethereum &&
coin != Coin.banano && if (ref.watch(pWallets.select((value) => value.getWallet(walletId)))
coin != Coin.nano && is MultiAddressInterface ||
coin != Coin.stellar && supportsSpark)
coin != Coin.stellarTestnet &&
coin != Coin.tezos)
const SizedBox( const SizedBox(
height: 20, height: 20,
), ),
if (coin != Coin.epicCash &&
coin != Coin.ethereum && if (ref.watch(pWallets.select((value) => value.getWallet(walletId)))
coin != Coin.banano && is MultiAddressInterface ||
coin != Coin.nano && supportsSpark)
coin != Coin.stellar &&
coin != Coin.stellarTestnet &&
coin != Coin.tezos)
SecondaryButton( SecondaryButton(
buttonHeight: ButtonHeight.l, buttonHeight: ButtonHeight.l,
onPressed: generateNewAddress, onPressed: supportsSpark && _showSparkAddress
? generateNewSparkAddress
: generateNewAddress,
label: "Generate new address", label: "Generate new address",
), ),
const SizedBox( const SizedBox(
@ -226,7 +477,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
child: QrImageView( child: QrImageView(
data: AddressUtils.buildUriString( data: AddressUtils.buildUriString(
coin, coin,
receivingAddress, _qrcodeContent ?? "",
{}, {},
), ),
size: 200, size: 200,
@ -267,7 +518,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
RouteGenerator.generateRoute( RouteGenerator.generateRoute(
RouteSettings( RouteSettings(
name: GenerateUriQrCodeView.routeName, name: GenerateUriQrCodeView.routeName,
arguments: Tuple2(coin, receivingAddress), arguments: Tuple2(coin, _qrcodeContent ?? ""),
), ),
), ),
], ],
@ -284,7 +535,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
builder: (_) => GenerateUriQrCodeView( builder: (_) => GenerateUriQrCodeView(
coin: coin, coin: coin,
receivingAddress: receivingAddress, receivingAddress: _qrcodeContent ?? "",
), ),
settings: const RouteSettings( settings: const RouteSettings(
name: GenerateUriQrCodeView.routeName, name: GenerateUriQrCodeView.routeName,

View file

@ -48,10 +48,12 @@ import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.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/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/models/tx_data.dart'; import 'package:stackwallet/wallets/models/tx_data.dart';
import 'package:stackwallet/wallets/wallet/impl/firo_wallet.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/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/animated_text.dart';
import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
@ -112,7 +114,6 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
String? _note; String? _note;
String? _onChainNote; String? _onChainNote;
Amount? _amountToSend;
Amount? _cachedAmountToSend; Amount? _cachedAmountToSend;
String? _address; String? _address;
@ -137,20 +138,22 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
Future<void> previewSend() async { Future<void> previewSend() async {
final wallet = ref.read(pWallets).getWallet(walletId); final wallet = ref.read(pWallets).getWallet(walletId);
final Amount amount = _amountToSend!; final Amount amount = ref.read(pSendAmount)!;
final Amount availableBalance; final Amount availableBalance;
if ((coin == Coin.firo || coin == Coin.firoTestNet)) { if ((coin == Coin.firo || coin == Coin.firoTestNet)) {
if (ref.read(publicPrivateBalanceStateProvider.state).state == switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
"Private") { case FiroType.public:
availableBalance = wallet.info.cachedBalance.spendable; availableBalance = wallet.info.cachedBalance.spendable;
// (manager.wallet as FiroWallet).availablePrivateBalance(); break;
} else { case FiroType.lelantus:
availableBalance = wallet.info.cachedBalanceSecondary.spendable; availableBalance = wallet.info.cachedBalanceSecondary.spendable;
// (manager.wallet as FiroWallet).availablePublicBalance(); break;
case FiroType.spark:
availableBalance = wallet.info.cachedBalanceTertiary.spendable;
break;
} }
} else { } else {
availableBalance = wallet.info.cachedBalance.spendable; availableBalance = wallet.info.cachedBalance.spendable;
;
} }
final coinControlEnabled = final coinControlEnabled =
@ -312,14 +315,71 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
: null, : null,
), ),
); );
} else if (wallet is FiroWallet && } else if (wallet is FiroWallet) {
ref.read(publicPrivateBalanceStateProvider.state).state == switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
"Private") { 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( txDataFuture = wallet.prepareSendLelantus(
txData: TxData( txData: TxData(
recipients: [(address: _address!, amount: amount)], 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 { } else {
final memo = isStellar ? memoController.text : null; final memo = isStellar ? memoController.text : null;
txDataFuture = wallet.prepareSend( 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) { if (mounted) {
// pop building dialog // pop building dialog
Navigator.of( Navigator.of(
@ -469,21 +530,21 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse( final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse(
cryptoAmountController.text, cryptoAmountController.text,
); );
final Amount? amount;
if (cryptoAmount != null) { if (cryptoAmount != null) {
_amountToSend = cryptoAmount; amount = cryptoAmount;
if (_cachedAmountToSend != null && if (_cachedAmountToSend != null && _cachedAmountToSend == amount) {
_cachedAmountToSend == _amountToSend) {
return; return;
} }
Logging.instance.log("it changed $_amountToSend $_cachedAmountToSend", Logging.instance.log("it changed $amount $_cachedAmountToSend",
level: LogLevel.Info); level: LogLevel.Info);
_cachedAmountToSend = _amountToSend; _cachedAmountToSend = amount;
final price = final price =
ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1;
if (price > Decimal.zero) { if (price > Decimal.zero) {
final String fiatAmountString = (_amountToSend!.decimal * price) final String fiatAmountString = (amount!.decimal * price)
.toAmount(fractionDigits: 2) .toAmount(fractionDigits: 2)
.fiatString( .fiatString(
locale: ref.read(localeServiceChangeNotifierProvider).locale, locale: ref.read(localeServiceChangeNotifierProvider).locale,
@ -492,44 +553,29 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
baseAmountController.text = fiatAmountString; baseAmountController.text = fiatAmountString;
} }
} else { } else {
_amountToSend = null; amount = null;
_cachedAmountToSend = null; _cachedAmountToSend = null;
baseAmountController.text = ""; baseAmountController.text = "";
} }
_updatePreviewButtonState(_address, _amountToSend); ref.read(pSendAmount.notifier).state = amount;
} }
} }
String? _updateInvalidAddressText(String address) { // String? _updateInvalidAddressText(String address) {
if (_data != null && _data!.contactLabel == address) { // if (_data != null && _data!.contactLabel == address) {
return null; // return null;
} // }
if (address.isNotEmpty && // if (address.isNotEmpty &&
!ref // !ref
.read(pWallets) // .read(pWallets)
.getWallet(walletId) // .getWallet(walletId)
.cryptoCurrency // .cryptoCurrency
.validateAddress(address)) { // .validateAddress(address)) {
return "Invalid address"; // return "Invalid address";
} // }
return null; // 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);
}
}
Future<void> scanQr() async { Future<void> scanQr() async {
try { try {
@ -567,10 +613,9 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
cryptoAmountController.text = ref cryptoAmountController.text = ref
.read(pAmountFormatter(coin)) .read(pAmountFormatter(coin))
.format(amount, withUnitName: false); .format(amount, withUnitName: false);
_amountToSend = amount; ref.read(pSendAmount.notifier).state = amount;
} }
_updatePreviewButtonState(_address, _amountToSend);
setState(() { setState(() {
_addressToggleFlag = sendToController.text.isNotEmpty; _addressToggleFlag = sendToController.text.isNotEmpty;
}); });
@ -584,7 +629,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
_address = qrResult.rawContent; _address = qrResult.rawContent;
sendToController.text = _address ?? ""; sendToController.text = _address ?? "";
_updatePreviewButtonState(_address, _amountToSend); _setValidAddressProviders(_address);
setState(() { setState(() {
_addressToggleFlag = sendToController.text.isNotEmpty; _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 { Future<void> pasteAddress() async {
final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain); final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain);
if (data?.text != null && data!.text!.isNotEmpty) { if (data?.text != null && data!.text!.isNotEmpty) {
@ -614,7 +678,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
sendToController.text = content; sendToController.text = content;
_address = content; _address = content;
_updatePreviewButtonState(_address, _amountToSend); _setValidAddressProviders(_address);
setState(() { setState(() {
_addressToggleFlag = sendToController.text.isNotEmpty; _addressToggleFlag = sendToController.text.isNotEmpty;
}); });
@ -643,28 +707,29 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
baseAmountString, baseAmountString,
locale: ref.read(localeServiceChangeNotifierProvider).locale, locale: ref.read(localeServiceChangeNotifierProvider).locale,
); );
final Amount? amount;
if (baseAmount != null) { if (baseAmount != null) {
final _price = final _price =
ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1;
if (_price == Decimal.zero) { if (_price == Decimal.zero) {
_amountToSend = Decimal.zero.toAmount(fractionDigits: coin.decimals); amount = Decimal.zero.toAmount(fractionDigits: coin.decimals);
} else { } else {
_amountToSend = baseAmount <= Amount.zero amount = baseAmount <= Amount.zero
? Decimal.zero.toAmount(fractionDigits: coin.decimals) ? Decimal.zero.toAmount(fractionDigits: coin.decimals)
: (baseAmount.decimal / _price) : (baseAmount.decimal / _price)
.toDecimal(scaleOnInfinitePrecision: coin.decimals) .toDecimal(scaleOnInfinitePrecision: coin.decimals)
.toAmount(fractionDigits: coin.decimals); .toAmount(fractionDigits: coin.decimals);
} }
if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) { if (_cachedAmountToSend != null && _cachedAmountToSend == amount) {
return; return;
} }
_cachedAmountToSend = _amountToSend; _cachedAmountToSend = amount;
Logging.instance.log("it changed $_amountToSend $_cachedAmountToSend", Logging.instance
level: LogLevel.Info); .log("it changed $amount $_cachedAmountToSend", level: LogLevel.Info);
final amountString = ref.read(pAmountFormatter(coin)).format( final amountString = ref.read(pAmountFormatter(coin)).format(
_amountToSend!, amount!,
withUnitName: false, withUnitName: false,
); );
@ -672,7 +737,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
cryptoAmountController.text = amountString; cryptoAmountController.text = amountString;
_cryptoAmountChangeLock = false; _cryptoAmountChangeLock = false;
} else { } else {
_amountToSend = Decimal.zero.toAmount(fractionDigits: coin.decimals); amount = Decimal.zero.toAmount(fractionDigits: coin.decimals);
_cryptoAmountChangeLock = true; _cryptoAmountChangeLock = true;
cryptoAmountController.text = ""; cryptoAmountController.text = "";
_cryptoAmountChangeLock = false; _cryptoAmountChangeLock = false;
@ -682,17 +747,29 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
// Format.decimalAmountToSatoshis( // Format.decimalAmountToSatoshis(
// _amountToSend!)); // _amountToSend!));
// }); // });
_updatePreviewButtonState(_address, _amountToSend); ref.read(pSendAmount.notifier).state = amount;
} }
Future<void> sendAllTapped() async { Future<void> sendAllTapped() async {
final info = ref.read(pWalletInfo(walletId)); final info = ref.read(pWalletInfo(walletId));
if ((coin == Coin.firo || coin == Coin.firoTestNet) && if (coin == Coin.firo || coin == Coin.firoTestNet) {
ref.read(publicPrivateBalanceStateProvider.state).state == "Private") { 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 cryptoAmountController.text = info
.cachedBalanceSecondary.spendable.decimal .cachedBalanceSecondary.spendable.decimal
.toStringAsFixed(coin.decimals); .toStringAsFixed(coin.decimals);
break;
case FiroType.spark:
cryptoAmountController.text = info
.cachedBalanceTertiary.spendable.decimal
.toStringAsFixed(coin.decimals);
break;
}
} else { } else {
cryptoAmountController.text = cryptoAmountController.text =
info.cachedBalance.spendable.decimal.toStringAsFixed(coin.decimals); info.cachedBalance.spendable.decimal.toStringAsFixed(coin.decimals);
@ -700,11 +777,12 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
} }
void _showDesktopCoinControl() async { void _showDesktopCoinControl() async {
final amount = ref.read(pSendAmount);
await showDialog<void>( await showDialog<void>(
context: context, context: context,
builder: (context) => DesktopCoinControlUseDialog( builder: (context) => DesktopCoinControlUseDialog(
walletId: widget.walletId, walletId: widget.walletId,
amountToSend: _amountToSend, amountToSend: amount,
), ),
); );
} }
@ -713,7 +791,8 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
void initState() { void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
ref.refresh(feeSheetSessionCacheProvider); ref.refresh(feeSheetSessionCacheProvider);
ref.read(previewTxButtonStateProvider.state).state = false; ref.read(pValidSendToAddress.state).state = false;
ref.read(pValidSparkSendToAddress.state).state = false;
}); });
// _calculateFeesFuture = calculateFees(0); // _calculateFeesFuture = calculateFees(0);
@ -748,20 +827,20 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
_cryptoFocus.addListener(() { _cryptoFocus.addListener(() {
if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) {
if (_amountToSend == null) { if (ref.read(pSendAmount) == null) {
ref.refresh(sendAmountProvider); ref.refresh(sendAmountProvider);
} else { } else {
ref.read(sendAmountProvider.state).state = _amountToSend!; ref.read(sendAmountProvider.state).state = ref.read(pSendAmount)!;
} }
} }
}); });
_baseFocus.addListener(() { _baseFocus.addListener(() {
if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) {
if (_amountToSend == null) { if (ref.read(pSendAmount) == null) {
ref.refresh(sendAmountProvider); ref.refresh(sendAmountProvider);
} else { } 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( const SizedBox(
height: 4, height: 4,
), ),
if (coin == Coin.firo) if (coin == Coin.firo || coin == Coin.firoTestNet)
Text( Text(
"Send from", "Send from",
style: STextStyles.desktopTextExtraSmall(context).copyWith( style: STextStyles.desktopTextExtraSmall(context).copyWith(
@ -831,22 +910,42 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
), ),
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
if (coin == Coin.firo) if (coin == Coin.firo || coin == Coin.firoTestNet)
const SizedBox( const SizedBox(
height: 10, height: 10,
), ),
if (coin == Coin.firo) if (coin == Coin.firo || coin == Coin.firoTestNet)
DropdownButtonHideUnderline( DropdownButtonHideUnderline(
child: DropdownButton2( child: DropdownButton2(
isExpanded: true, isExpanded: true,
value: ref.watch(publicPrivateBalanceStateProvider.state).state, value: ref.watch(publicPrivateBalanceStateProvider.state).state,
items: [ items: [
DropdownMenuItem( DropdownMenuItem(
value: "Private", value: FiroType.spark,
child: Row( child: Row(
children: [ children: [
Text( 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), style: STextStyles.itemSubtitle12(context),
), ),
const SizedBox( const SizedBox(
@ -862,7 +961,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
), ),
), ),
DropdownMenuItem( DropdownMenuItem(
value: "Public", value: FiroType.public,
child: Row( child: Row(
children: [ children: [
Text( Text(
@ -882,9 +981,9 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
), ),
], ],
onChanged: (value) { onChanged: (value) {
if (value is String) { if (value is FiroType) {
setState(() { setState(() {
ref.watch(publicPrivateBalanceStateProvider.state).state = ref.read(publicPrivateBalanceStateProvider.state).state =
value; value;
}); });
} }
@ -917,7 +1016,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
), ),
), ),
), ),
if (coin == Coin.firo) if (coin == Coin.firo || coin == Coin.firoTestNet)
const SizedBox( const SizedBox(
height: 20, height: 20,
), ),
@ -1159,7 +1258,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
), ),
onChanged: (newValue) { onChanged: (newValue) {
_address = newValue; _address = newValue;
_updatePreviewButtonState(_address, _amountToSend); _setValidAddressProviders(_address);
setState(() { setState(() {
_addressToggleFlag = newValue.isNotEmpty; _addressToggleFlag = newValue.isNotEmpty;
@ -1199,8 +1298,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
onTap: () { onTap: () {
sendToController.text = ""; sendToController.text = "";
_address = ""; _address = "";
_updatePreviewButtonState( _setValidAddressProviders(_address);
_address, _amountToSend);
setState(() { setState(() {
_addressToggleFlag = false; _addressToggleFlag = false;
}); });
@ -1261,10 +1359,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
_address = entry.address; _address = entry.address;
_updatePreviewButtonState( _setValidAddressProviders(_address);
_address,
_amountToSend,
);
setState(() { setState(() {
_addressToggleFlag = true; _addressToggleFlag = true;
@ -1289,9 +1384,44 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
if (!isPaynymSend) if (!isPaynymSend)
Builder( Builder(
builder: (_) { builder: (_) {
final error = _updateInvalidAddressText( final String? error;
_address ?? "",
); 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) { if (error == null || error.isEmpty) {
return Container(); 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( const SizedBox(
height: 10, height: 10,
), ),
if (isStellar) if (isStellar ||
(ref.watch(pValidSparkSendToAddress) &&
ref.watch(publicPrivateBalanceStateProvider) !=
FiroType.lelantus))
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius, Constants.size.circularBorderRadius,
@ -1387,8 +1523,12 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
ConditionalParent( ConditionalParent(
condition: coin.isElectrumXCoin && condition: coin.isElectrumXCoin &&
!(((coin == Coin.firo || coin == Coin.firoTestNet) && !(((coin == Coin.firo || coin == Coin.firoTestNet) &&
ref.read(publicPrivateBalanceStateProvider.state).state == (ref.watch(publicPrivateBalanceStateProvider.state).state ==
"Private")), FiroType.lelantus ||
ref
.watch(publicPrivateBalanceStateProvider.state)
.state ==
FiroType.spark))),
builder: (child) => Row( builder: (child) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -1487,14 +1627,32 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
publicPrivateBalanceStateProvider publicPrivateBalanceStateProvider
.state) .state)
.state != .state !=
"Private") { FiroType.public) {
throw UnimplementedError("FIXME"); final firoWallet = wallet as FiroWallet;
// TODO: [prio=high] firo fee fix
// ref if (ref
// .read(feeSheetSessionCacheProvider) .read(
// .average[amount] = await (manager.wallet publicPrivateBalanceStateProvider
// as FiroWallet) .state)
// .estimateFeeForPublic(amount, feeRate); .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 { } else {
ref ref
.read(feeSheetSessionCacheProvider) .read(feeSheetSessionCacheProvider)
@ -1532,7 +1690,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
.watch( .watch(
publicPrivateBalanceStateProvider.state) publicPrivateBalanceStateProvider.state)
.state == .state ==
"Private" FiroType.lelantus
? Text( ? Text(
"~${ref.watch(pAmountFormatter(coin)).format( "~${ref.watch(pAmountFormatter(coin)).format(
Amount( Amount(
@ -1595,10 +1753,9 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
PrimaryButton( PrimaryButton(
buttonHeight: ButtonHeight.l, buttonHeight: ButtonHeight.l,
label: "Preview send", label: "Preview send",
enabled: ref.watch(previewTxButtonStateProvider.state).state, enabled: ref.watch(pPreviewTxButtonEnabled(coin)),
onPressed: ref.watch(previewTxButtonStateProvider.state).state onPressed:
? previewSend ref.watch(pPreviewTxButtonEnabled(coin)) ? previewSend : null,
: null,
) )
], ],
); );

View file

@ -198,7 +198,8 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
} }
try { try {
await firoWallet.anonymizeAllPublicFunds(); // await firoWallet.anonymizeAllLelantus();
await firoWallet.anonymizeAllSpark();
shouldPop = true; shouldPop = true;
if (context.mounted) { if (context.mounted) {
Navigator.of(context, rootNavigator: true).pop(); Navigator.of(context, rootNavigator: true).pop();

View file

@ -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/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/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/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/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/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/themes/stack_colors.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 coin = ref.watch(pWalletCoin(widget.walletId));
final isFiro = coin == Coin.firo || coin == Coin.firoTestNet;
final locale = ref.watch( final locale = ref.watch(
localeServiceChangeNotifierProvider.select((value) => value.locale)); localeServiceChangeNotifierProvider.select((value) => value.locale));
@ -82,29 +84,30 @@ class _WDesktopWalletSummaryState extends ConsumerState<DesktopWalletSummary> {
ref.watch(walletBalanceToggleStateProvider.state).state == ref.watch(walletBalanceToggleStateProvider.state).state ==
WalletBalanceToggleState.available; 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 Balance balance = widget.isToken
? ref.watch(tokenServiceProvider.select((value) => value!.balance)) ? ref.watch(tokenServiceProvider.select((value) => value!.balance))
: ref.watch(pWalletBalance(walletId)); : ref.watch(pWalletBalance(walletId));
Amount balanceToShow; balanceToShow = _showAvailable ? balance.spendable : balance.total;
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;
}
} }
return Consumer( return Consumer(

View file

@ -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/addresses/desktop_wallet_addresses_view.dart';
import 'package:stackwallet/pages_desktop_specific/lelantus_coins/lelantus_coins_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/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/route_generator.dart';
import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
@ -32,7 +33,8 @@ enum _WalletOptions {
deleteWallet, deleteWallet,
changeRepresentative, changeRepresentative,
showXpub, showXpub,
lelantusCoins; lelantusCoins,
sparkCoins;
String get prettyName { String get prettyName {
switch (this) { switch (this) {
@ -46,6 +48,8 @@ enum _WalletOptions {
return "Show xPub"; return "Show xPub";
case _WalletOptions.lelantusCoins: case _WalletOptions.lelantusCoins:
return "Lelantus Coins"; return "Lelantus Coins";
case _WalletOptions.sparkCoins:
return "Spark Coins";
} }
} }
} }
@ -89,6 +93,9 @@ class WalletOptionsButton extends StatelessWidget {
onFiroShowLelantusCoins: () async { onFiroShowLelantusCoins: () async {
Navigator.of(context).pop(_WalletOptions.lelantusCoins); Navigator.of(context).pop(_WalletOptions.lelantusCoins);
}, },
onFiroShowSparkCoins: () async {
Navigator.of(context).pop(_WalletOptions.sparkCoins);
},
walletId: walletId, walletId: walletId,
); );
}, },
@ -191,6 +198,15 @@ class WalletOptionsButton extends StatelessWidget {
), ),
); );
break; 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.onShowXpubPressed,
required this.onChangeRepPressed, required this.onChangeRepPressed,
required this.onFiroShowLelantusCoins, required this.onFiroShowLelantusCoins,
required this.onFiroShowSparkCoins,
required this.walletId, required this.walletId,
}) : super(key: key); }) : super(key: key);
@ -232,6 +249,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget {
final VoidCallback onShowXpubPressed; final VoidCallback onShowXpubPressed;
final VoidCallback onChangeRepPressed; final VoidCallback onChangeRepPressed;
final VoidCallback onFiroShowLelantusCoins; final VoidCallback onFiroShowLelantusCoins;
final VoidCallback onFiroShowSparkCoins;
final String walletId; final String walletId;
@override @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) if (xpubEnabled)
const SizedBox( const SizedBox(
height: 8, height: 8,

View file

@ -65,7 +65,14 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
bool get fieldsMatch => bool get fieldsMatch =>
passwordController.text == passwordRepeatController.text; passwordController.text == passwordRepeatController.text;
bool _nextLock = false;
void onNextPressed() async { void onNextPressed() async {
if (_nextLock) {
return;
}
_nextLock = true;
final String passphrase = passwordController.text; final String passphrase = passwordController.text;
final String repeatPassphrase = passwordRepeatController.text; final String repeatPassphrase = passwordRepeatController.text;
@ -75,6 +82,7 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
message: "A password is required", message: "A password is required",
context: context, context: context,
)); ));
_nextLock = false;
return; return;
} }
if (passphrase != repeatPassphrase) { if (passphrase != repeatPassphrase) {
@ -83,6 +91,7 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
message: "Password does not match", message: "Password does not match",
context: context, context: context,
)); ));
_nextLock = false;
return; return;
} }
@ -106,6 +115,7 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
message: "Error: $e", message: "Error: $e",
context: context, context: context,
)); ));
_nextLock = false;
return; return;
} }
@ -132,6 +142,7 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
context: context, context: context,
)); ));
} }
_nextLock = false;
} }
@override @override

View file

@ -79,11 +79,18 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
} }
} }
bool _loginLock = false;
Future<void> login() async { Future<void> login() async {
if (_loginLock) {
return;
}
_loginLock = true;
try { try {
unawaited( unawaited(
showDialog( showDialog(
context: context, context: context,
barrierDismissible: false,
builder: (context) => const Column( builder: (context) => const Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
@ -138,6 +145,8 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
context: context, context: context,
); );
} }
} finally {
_loginLock = false;
} }
} }

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

View file

@ -9,9 +9,32 @@
*/ */
import 'package:flutter_riverpod/flutter_riverpod.dart'; 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>((_) { final pSendAmount = StateProvider.autoDispose<Amount?>((_) => null);
return false; 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>((_) { final previewTokenTxButtonStateProvider = StateProvider.autoDispose<bool>((_) {

View file

@ -10,5 +10,11 @@
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
enum FiroType {
public,
lelantus,
spark;
}
final publicPrivateBalanceStateProvider = final publicPrivateBalanceStateProvider =
StateProvider<String>((_) => "Private"); StateProvider<FiroType>((_) => FiroType.lelantus);

View file

@ -14,7 +14,3 @@ import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
final walletBalanceToggleStateProvider = final walletBalanceToggleStateProvider =
StateProvider.autoDispose<WalletBalanceToggleState>( StateProvider.autoDispose<WalletBalanceToggleState>(
(ref) => WalletBalanceToggleState.full); (ref) => WalletBalanceToggleState.full);
final walletPrivateBalanceToggleStateProvider =
StateProvider.autoDispose<WalletBalanceToggleState>(
(ref) => WalletBalanceToggleState.full);

View file

@ -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/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/syncing_preferences_settings.dart';
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/tor_settings/tor_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/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/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/amount/amount.dart';
@ -1858,6 +1859,20 @@ class RouteGenerator {
} }
return _routeError("${settings.name} invalid args: ${args.toString()}"); 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: case DesktopCoinControlView.routeName:
if (args is String) { if (args is String) {
return getRoute( return getRoute(

View file

@ -611,7 +611,8 @@ abstract class EthereumAPI {
try { try {
final response = await client.get( final response = await client.get(
url: Uri.parse( url: Uri.parse(
"$stackBaseServer/tokens?addrs=$contractAddress&parts=all", // "$stackBaseServer/tokens?addrs=$contractAddress&parts=all",
"$stackBaseServer/names?terms=$contractAddress",
), ),
proxyInfo: Prefs.instance.useTor proxyInfo: Prefs.instance.useTor
? TorService.sharedInstance.getProxyInfo() ? TorService.sharedInstance.getProxyInfo()
@ -621,6 +622,10 @@ abstract class EthereumAPI {
if (response.code == 200) { if (response.code == 200) {
final json = jsonDecode(response.body) as Map; final json = jsonDecode(response.body) as Map;
if (json["data"] is List) { 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); final map = Map<String, dynamic>.from(json["data"].first as Map);
EthContract? token; EthContract? token;
if (map["isErc20"] == true) { if (map["isErc20"] == true) {

View file

@ -113,6 +113,7 @@ mixin ElectrumXParsing {
outputs: List.unmodifiable(outputs), outputs: List.unmodifiable(outputs),
subType: TransactionSubType.none, subType: TransactionSubType.none,
type: TransactionType.unknown, type: TransactionType.unknown,
otherData: null,
); );
} }

View file

@ -35,8 +35,6 @@ class Wallets {
late NodeService nodeService; late NodeService nodeService;
late MainDB mainDB; late MainDB mainDB;
bool get hasWallets => _wallets.isNotEmpty;
List<Wallet> get wallets => _wallets.values.toList(); List<Wallet> get wallets => _wallets.values.toList();
static bool hasLoaded = false; static bool hasLoaded = false;

View file

@ -26,6 +26,10 @@ class Amount {
fractionDigits: 0, fractionDigits: 0,
); );
Amount.zeroWith({required this.fractionDigits})
: assert(fractionDigits >= 0),
_value = BigInt.zero;
/// truncate decimal value to [fractionDigits] places /// truncate decimal value to [fractionDigits] places
Amount.fromDecimal(Decimal amount, {required this.fractionDigits}) Amount.fromDecimal(Decimal amount, {required this.fractionDigits})
: assert(fractionDigits >= 0), : assert(fractionDigits >= 0),

View file

@ -42,7 +42,7 @@ enum Coin {
stellarTestnet, stellarTestnet,
} }
final int kTestNetCoinCount = 5; // Util.isDesktop ? 5 : 4; final int kTestNetCoinCount = 6; // Util.isDesktop ? 5 : 4;
// remove firotestnet for now // remove firotestnet for now
extension CoinExt on Coin { extension CoinExt on Coin {

View file

@ -20,16 +20,18 @@ abstract class StackFileSystem {
static Future<Directory> applicationRootDirectory() async { static Future<Directory> applicationRootDirectory() async {
Directory appDirectory; 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? // todo: can merge and do same as regular linux home dir?
if (Logging.isArmLinux) { if (Logging.isArmLinux) {
appDirectory = await getApplicationDocumentsDirectory(); appDirectory = await getApplicationDocumentsDirectory();
appDirectory = Directory("${appDirectory.path}/.stackwallet"); appDirectory = Directory("${appDirectory.path}/.$dirName");
} else if (Platform.isLinux) { } else if (Platform.isLinux) {
if (overrideDir != null) { if (overrideDir != null) {
appDirectory = Directory(overrideDir!); appDirectory = Directory(overrideDir!);
} else { } else {
appDirectory = appDirectory = Directory("${Platform.environment['HOME']}/.$dirName");
Directory("${Platform.environment['HOME']}/.stackwallet");
} }
} else if (Platform.isWindows) { } else if (Platform.isWindows) {
if (overrideDir != null) { if (overrideDir != null) {
@ -42,7 +44,7 @@ abstract class StackFileSystem {
appDirectory = Directory(overrideDir!); appDirectory = Directory(overrideDir!);
} else { } else {
appDirectory = await getLibraryDirectory(); appDirectory = await getLibraryDirectory();
appDirectory = Directory("${appDirectory.path}/stackwallet"); appDirectory = Directory("${appDirectory.path}/$dirName");
} }
} else if (Platform.isIOS) { } else if (Platform.isIOS) {
// todo: check if we need different behaviour here // todo: check if we need different behaviour here

View file

@ -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/utilities/enums/derive_path_type_enum.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.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/crypto_currency/intermediate/bip39_hd_currency.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
class Firo extends Bip39HDCurrency { class Firo extends Bip39HDCurrency {
Firo(super.network) { Firo(super.network) {
@ -132,9 +133,15 @@ class Firo extends Bip39HDCurrency {
coinlib.Address.fromString(address, networkParams); coinlib.Address.fromString(address, networkParams);
return true; return true;
} catch (_) { } 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 @override

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

File diff suppressed because it is too large Load diff

View file

@ -55,6 +55,15 @@ class TxData {
// tezos specific // tezos specific
final tezart.OperationsList? tezosOperationsList; final tezart.OperationsList? tezosOperationsList;
// firo spark specific
final List<
({
String address,
Amount amount,
String memo,
})>? sparkRecipients;
final List<TxData>? sparkMints;
TxData({ TxData({
this.feeRateType, this.feeRateType,
this.feeRateAmount, this.feeRateAmount,
@ -85,6 +94,8 @@ class TxData {
this.txSubType, this.txSubType,
this.mintsMapLelantus, this.mintsMapLelantus,
this.tezosOperationsList, this.tezosOperationsList,
this.sparkRecipients,
this.sparkMints,
}); });
Amount? get amount => recipients != null && recipients!.isNotEmpty Amount? get amount => recipients != null && recipients!.isNotEmpty
@ -93,6 +104,13 @@ class TxData {
.reduce((total, amount) => total += amount) .reduce((total, amount) => total += amount)
: null; : 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 int? get estimatedSatsPerVByte => fee != null && vSize != null
? (fee!.raw ~/ BigInt.from(vSize!)).toInt() ? (fee!.raw ~/ BigInt.from(vSize!)).toInt()
: null; : null;
@ -127,6 +145,14 @@ class TxData {
TransactionSubType? txSubType, TransactionSubType? txSubType,
List<Map<String, dynamic>>? mintsMapLelantus, List<Map<String, dynamic>>? mintsMapLelantus,
tezart.OperationsList? tezosOperationsList, tezart.OperationsList? tezosOperationsList,
List<
({
String address,
Amount amount,
String memo,
})>?
sparkRecipients,
List<TxData>? sparkMints,
}) { }) {
return TxData( return TxData(
feeRateType: feeRateType ?? this.feeRateType, feeRateType: feeRateType ?? this.feeRateType,
@ -159,6 +185,8 @@ class TxData {
txSubType: txSubType ?? this.txSubType, txSubType: txSubType ?? this.txSubType,
mintsMapLelantus: mintsMapLelantus ?? this.mintsMapLelantus, mintsMapLelantus: mintsMapLelantus ?? this.mintsMapLelantus,
tezosOperationsList: tezosOperationsList ?? this.tezosOperationsList, tezosOperationsList: tezosOperationsList ?? this.tezosOperationsList,
sparkRecipients: sparkRecipients ?? this.sparkRecipients,
sparkMints: sparkMints ?? this.sparkMints,
); );
} }
@ -193,5 +221,7 @@ class TxData {
'txSubType: $txSubType, ' 'txSubType: $txSubType, '
'mintsMapLelantus: $mintsMapLelantus, ' 'mintsMapLelantus: $mintsMapLelantus, '
'tezosOperationsList: $tezosOperationsList, ' 'tezosOperationsList: $tezosOperationsList, '
'sparkRecipients: $sparkRecipients, '
'sparkMints: $sparkMints, '
'}'; '}';
} }

View file

@ -28,7 +28,7 @@ class BitcoinWallet extends Bip39HDWallet
// =========================================================================== // ===========================================================================
@override @override
Future<List<Address>> fetchAllOwnAddresses() async { Future<List<Address>> fetchAddressesForElectrumXScan() async {
final allAddresses = await mainDB final allAddresses = await mainDB
.getAddresses(walletId) .getAddresses(walletId)
.filter() .filter()
@ -51,7 +51,7 @@ class BitcoinWallet extends Bip39HDWallet
// TODO: [prio=med] switch to V2 transactions // TODO: [prio=med] switch to V2 transactions
final data = await fetchTransactionsV1( final data = await fetchTransactionsV1(
addresses: await fetchAllOwnAddresses(), addresses: await fetchAddressesForElectrumXScan(),
currentChainHeight: currentChainHeight, currentChainHeight: currentChainHeight,
); );

View file

@ -63,7 +63,7 @@ class BitcoincashWallet extends Bip39HDWallet
// =========================================================================== // ===========================================================================
@override @override
Future<List<Address>> fetchAllOwnAddresses() async { Future<List<Address>> fetchAddressesForElectrumXScan() async {
final allAddresses = await mainDB final allAddresses = await mainDB
.getAddresses(walletId) .getAddresses(walletId)
.filter() .filter()
@ -94,7 +94,7 @@ class BitcoincashWallet extends Bip39HDWallet
@override @override
Future<void> updateTransactions() async { Future<void> updateTransactions() async {
List<Address> allAddressesOld = await fetchAllOwnAddresses(); List<Address> allAddressesOld = await fetchAddressesForElectrumXScan();
Set<String> receivingAddresses = allAddressesOld Set<String> receivingAddresses = allAddressesOld
.where((e) => e.subType == AddressSubType.receiving) .where((e) => e.subType == AddressSubType.receiving)
@ -293,6 +293,7 @@ class BitcoincashWallet extends Bip39HDWallet
outputs: List.unmodifiable(outputs), outputs: List.unmodifiable(outputs),
type: type, type: type,
subType: subType, subType: subType,
otherData: null,
); );
txns.add(tx); txns.add(tx);

View file

@ -24,7 +24,7 @@ class DogecoinWallet extends Bip39HDWallet
// =========================================================================== // ===========================================================================
@override @override
Future<List<Address>> fetchAllOwnAddresses() async { Future<List<Address>> fetchAddressesForElectrumXScan() async {
final allAddresses = await mainDB final allAddresses = await mainDB
.getAddresses(walletId) .getAddresses(walletId)
.filter() .filter()
@ -47,7 +47,7 @@ class DogecoinWallet extends Bip39HDWallet
// TODO: [prio=med] switch to V2 transactions // TODO: [prio=med] switch to V2 transactions
final data = await fetchTransactionsV1( final data = await fetchTransactionsV1(
addresses: await fetchAllOwnAddresses(), addresses: await fetchAddressesForElectrumXScan(),
currentChainHeight: currentChainHeight, currentChainHeight: currentChainHeight,
); );

View file

@ -58,7 +58,7 @@ class EcashWallet extends Bip39HDWallet
// =========================================================================== // ===========================================================================
@override @override
Future<List<Address>> fetchAllOwnAddresses() async { Future<List<Address>> fetchAddressesForElectrumXScan() async {
final allAddresses = await mainDB final allAddresses = await mainDB
.getAddresses(walletId) .getAddresses(walletId)
.filter() .filter()
@ -87,7 +87,7 @@ class EcashWallet extends Bip39HDWallet
@override @override
Future<void> updateTransactions() async { Future<void> updateTransactions() async {
List<Address> allAddressesOld = await fetchAllOwnAddresses(); List<Address> allAddressesOld = await fetchAddressesForElectrumXScan();
Set<String> receivingAddresses = allAddressesOld Set<String> receivingAddresses = allAddressesOld
.where((e) => e.subType == AddressSubType.receiving) .where((e) => e.subType == AddressSubType.receiving)
@ -169,7 +169,7 @@ class EcashWallet extends Bip39HDWallet
final prevOut = OutputV2.fromElectrumXJson( final prevOut = OutputV2.fromElectrumXJson(
prevOutJson, prevOutJson,
decimalPlaces: cryptoCurrency.fractionDigits, decimalPlaces: cryptoCurrency.fractionDigits,
isECashFullAmountNotSats: true, isFullAmountNotSats: true,
walletOwns: false, // doesn't matter here as this is not saved walletOwns: false, // doesn't matter here as this is not saved
); );
@ -208,7 +208,7 @@ class EcashWallet extends Bip39HDWallet
OutputV2 output = OutputV2.fromElectrumXJson( OutputV2 output = OutputV2.fromElectrumXJson(
Map<String, dynamic>.from(outputJson as Map), Map<String, dynamic>.from(outputJson as Map),
decimalPlaces: cryptoCurrency.fractionDigits, decimalPlaces: cryptoCurrency.fractionDigits,
isECashFullAmountNotSats: true, isFullAmountNotSats: true,
// don't know yet if wallet owns. Need addresses first // don't know yet if wallet owns. Need addresses first
walletOwns: false, walletOwns: false,
); );
@ -288,6 +288,7 @@ class EcashWallet extends Bip39HDWallet
outputs: List.unmodifiable(outputs), outputs: List.unmodifiable(outputs),
type: type, type: type,
subType: subType, subType: subType,
otherData: null,
); );
txns.add(tx); txns.add(tx);

View file

@ -1,22 +1,25 @@
import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:decimal/decimal.dart'; import 'package:decimal/decimal.dart';
import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:stackwallet/db/hive/db.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/v2/input_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/input.dart'; import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/output.dart'; import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/isar/models/firo_specific/lelantus_coin.dart';
import 'package:stackwallet/utilities/amount/amount.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/logger.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart'; import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart';
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.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/intermediate/bip39_hd_wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.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/lelantus_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_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) const sparkStartBlock = 819300; // (approx 18 Jan 2024)
@ -27,6 +30,9 @@ class FiroWallet extends Bip39HDWallet
FiroWallet(CryptoCurrencyNetwork network) : super(Firo(network)); FiroWallet(CryptoCurrencyNetwork network) : super(Firo(network));
@override
int get isarTransactionVersion => 2;
@override @override
FilterOperation? get changeAddressFilterOperation => FilterOperation? get changeAddressFilterOperation =>
FilterGroup.and(standardChangeAddressFilters); 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 @override
Future<void> updateTransactions() async { 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) .where((e) => e.subType == AddressSubType.receiving)
.map((e) => e.value) .map((e) => convertAddressString(e.value))
.toSet(); .toSet();
Set<String> changeAddresses = allAddresses
Set<String> changeAddresses = allAddressesOld
.where((e) => e.subType == AddressSubType.change) .where((e) => e.subType == AddressSubType.change)
.map((e) => e.value) .map((e) => convertAddressString(e.value))
.toSet(); .toSet();
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
final List<Map<String, dynamic>> allTxHashes = 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 = []; List<Map<String, dynamic>> allTransactions = [];
@ -110,8 +110,6 @@ class FiroWallet extends Bip39HDWallet
} }
} }
// final currentHeight = await chainHeight;
for (final txHash in allTxHashes) { for (final txHash in allTxHashes) {
// final storedTx = await db // final storedTx = await db
// .getTransactions(walletId) // .getTransactions(walletId)
@ -127,508 +125,372 @@ class FiroWallet extends Bip39HDWallet
coin: info.coin, coin: info.coin,
); );
if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { // check for duplicates before adding to list
tx["address"] = await mainDB if (allTransactions
.getAddresses(walletId) .indexWhere((e) => e["txid"] == tx["txid"] as String) ==
.filter() -1) {
.valueEqualTo(txHash["address"] as String) tx["height"] ??= txHash["height"];
.findFirst();
tx["height"] = txHash["height"];
allTransactions.add(tx); allTransactions.add(tx);
} }
// } // }
} }
final List<Tuple2<Transaction, Address?>> txnsData = []; final List<TransactionV2> txns = [];
for (final txObject in allTransactions) { for (final txData in allTransactions) {
final inputList = txObject["vin"] as List; // set to true if any inputs were detected as owned by this wallet
final outputList = txObject["vout"] as List; 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 isMint = false;
bool isJMint = 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 final sparkCoinsInvolved =
for (final output in outputList) { sparkCoins.where((e) => e.txHash == txData["txid"]);
if (output["scriptPubKey"]?["type"] == "lelantusmint") { if (isMySpark && sparkCoinsInvolved.isEmpty) {
final asm = output["scriptPubKey"]?["asm"] as String?; 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 != null) {
if (asm.startsWith("OP_LELANTUSJMINT")) { if (asm.startsWith("OP_LELANTUSJMINT")) {
isJMint = true; isJMint = true;
break;
} else if (asm.startsWith("OP_LELANTUSMINT")) { } else if (asm.startsWith("OP_LELANTUSMINT")) {
isMint = true; isMint = true;
break;
} else { } else {
Logging.instance.log( 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, level: LogLevel.Error,
); );
} }
} else { } else {
Logging.instance.log( Logging.instance.log(
"ASM for lelantusmint tx: ${txObject["txid"]} is null!", "ASM for lelantusmint tx: ${txData["txid"]} is null!",
level: LogLevel.Error, 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 = {}; OutputV2 output = OutputV2.fromElectrumXJson(
Set<String> outputAddresses = {}; outMap,
decimalPlaces: cryptoCurrency.fractionDigits,
Amount totalInputValue = Amount( isFullAmountNotSats: true,
rawValue: BigInt.zero, // don't know yet if wallet owns. Need addresses first
fractionDigits: cryptoCurrency.fractionDigits, walletOwns: false,
);
Amount totalOutputValue = Amount(
rawValue: BigInt.zero,
fractionDigits: cryptoCurrency.fractionDigits,
); );
Amount amountSentFromWallet = Amount( // if (isSparkSpend) {
rawValue: BigInt.zero, // // TODO?
fractionDigits: cryptoCurrency.fractionDigits, // } else
); if (isSparkMint) {
Amount amountReceivedInWallet = Amount( if (isMySpark) {
rawValue: BigInt.zero, if (output.addresses.isEmpty &&
fractionDigits: cryptoCurrency.fractionDigits, output.scriptPubKeyHex.length >= 488) {
); // likely spark related
Amount changeAmount = Amount( final opByte = output.scriptPubKeyHex
rawValue: BigInt.zero, .substring(0, 2)
fractionDigits: cryptoCurrency.fractionDigits, .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 ================================================ if (coin == null) {
// We should be able to assume this belongs to this wallet // not ours
if (isMint) { } else {
List<Input> ins = []; output = output.copyWith(
walletOwns: true,
valueStringSats: coin.value.toString(),
addresses: [
coin.address,
],
);
}
}
}
}
} else if (isMint || isJMint) {
// do nothing extra ?
} else {
// TODO?
}
// Parse inputs // if output was to my wallet, add value to amount received
for (final input in inputList) { if (receivingAddresses
// Both value and address should not be null for a mint .intersection(output.addresses.toSet())
final address = input["address"] as String?; .isNotEmpty) {
final value = input["valueSat"] as int?; 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 outputs.add(output);
// wallet as any tx we look up will be looked up by one of this }
// wallet's addresses
if (address != null && value != null) { if (isJMint || isSparkSpend) {
totalInputValue += value.toAmountAsRaw( anonFees = Amount(
rawValue: BigInt.zero,
fractionDigits: cryptoCurrency.fractionDigits, fractionDigits: cryptoCurrency.fractionDigits,
); );
} }
ins.add( // parse inputs
Input( final List<InputV2> inputs = [];
txid: input['txid'] as String? ?? "", for (final jsonInput in txData["vin"] as List) {
vout: input['vout'] as int? ?? -1, final map = Map<String, dynamic>.from(jsonInput as Map);
scriptSig: input['scriptSig']?['hex'] as String?,
scriptSigAsm: input['scriptSig']?['asm'] as String?, final List<String> addresses = [];
isCoinbase: input['is_coinbase'] as bool?, String valueStringSats = "0";
sequence: input['sequence'] as int?, OutpointV2? outpoint;
innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?,
), 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 if (isSparkSpend) {
for (final output in outputList) { // anon fees
// get value final nFee = Decimal.tryParse(map["nFees"].toString());
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 (nFee != null) { if (nFee != null) {
final fees = Amount.fromDecimal( final fees = Amount.fromDecimal(
nFee, nFee,
fractionDigits: cryptoCurrency.fractionDigits, 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( if (address != null && value != null) {
Input( valueStringSats = value.toString();
txid: input['txid'] as String? ?? "", addresses.add(address);
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?,
),
);
} }
} 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; if (address != null && value != null) {
valueStringSats = value.toString();
// Parse outputs addresses.add(address);
List<Output> outs = []; }
for (final output in outputList) { } else if (isJMint) {
// get value // anon fees
final value = Amount.fromDecimal( final nFee = Decimal.tryParse(map["nFees"].toString());
Decimal.parse(output["value"].toString()), if (nFee != null) {
final fees = Amount.fromDecimal(
nFee,
fractionDigits: cryptoCurrency.fractionDigits, fractionDigits: cryptoCurrency.fractionDigits,
); );
// add value to total anonFees = anonFees! + fees;
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;
} }
} } else if (coinbase == null && txid != null && vout != null) {
final inputTx = await electrumXCachedClient.getTransaction(
outs.add( txHash: txid,
Output( coin: cryptoCurrency.coin,
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,
); );
final tx = Transaction( final prevOutJson = Map<String, dynamic>.from(
walletId: walletId, (inputTx["vout"] as List).firstWhere((e) => e["n"] == vout)
txid: txid, as Map);
timestamp: txObject["blocktime"] as int? ??
(DateTime.now().millisecondsSinceEpoch ~/ 1000), final prevOut = OutputV2.fromElectrumXJson(
type: type, prevOutJson,
subType: subType, decimalPlaces: cryptoCurrency.fractionDigits,
amount: amount.raw.toInt(), isFullAmountNotSats: true,
amountString: amount.toJsonString(), walletOwns: false, // doesn't matter here as this is not saved
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,
); );
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 ===================================== InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor(
} else if (inputList.length == 1 && scriptSigHex: map["scriptSig"]?["hex"] as String?,
inputList.first["coinbase"] is String) { sequence: map["sequence"] as int?,
List<Input> ins = [ outpoint: outpoint,
Input( valueStringSats: valueStringSats,
txid: inputList.first["coinbase"] as String, addresses: addresses,
vout: -1, witness: map["witness"] as String?,
scriptSig: null, coinbase: coinbase,
scriptSigAsm: null, innerRedeemScriptAsm: map["innerRedeemscriptAsm"] as String?,
isCoinbase: true, // don't know yet if wallet owns. Need addresses first
sequence: inputList.first['sequence'] as int?, walletOwns: false,
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,
); );
// get output address if (allAddressesSet.intersection(input.addresses.toSet()).isNotEmpty) {
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? wasSentFromThisWallet = true;
output["scriptPubKey"]?["address"] as String?; input = input.copyWith(walletOwns: true);
if (address != null) { } else if (isMySpark) {
outputAddresses.add(address); final lTags = map["lTags"] as List?;
// if output was to my wallet, add value to amount received if (lTags?.isNotEmpty == true) {
if (receivingAddresses.contains(address)) { final List<SparkCoin> usedCoins = [];
amountReceivedInWallet += value; 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( if (usedCoins.isNotEmpty) {
Output( input = input.copyWith(
scriptPubKey: output['scriptPubKey']?['hex'] as String?, addresses: usedCoins.map((e) => e.address).toList(),
scriptPubKeyAsm: output['scriptPubKey']?['asm'] as String?, valueStringSats: usedCoins
scriptPubKeyType: output['scriptPubKey']?['type'] as String?, .map((e) => e.value)
scriptPubKeyAddress: address ?? "", .reduce((value, element) => value += element)
value: value.raw.toInt(), .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( inputs.add(input);
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 outputs final totalOut = outputs
List<Output> outs = []; .map((e) => e.value)
for (final output in outputList) { .fold(BigInt.zero, (value, element) => value + element);
// 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;
TransactionType type; TransactionType type;
Amount amount; TransactionSubType subType = TransactionSubType.none;
if (mySentFromAddresses.isNotEmpty &&
myReceivedOnAddresses.isNotEmpty) {
// tx is sent to self
type = TransactionType.sentToSelf;
// should be 0 // TODO integrate the following with the next bit
amount = amountSentFromWallet - if (isSparkSpend) {
amountReceivedInWallet - subType = TransactionSubType.sparkSpend;
fee - } else if (isSparkMint) {
changeAmount; subType = TransactionSubType.sparkMint;
} else if (mySentFromAddresses.isNotEmpty) { } else if (isMint) {
// outgoing tx subType = TransactionSubType.mint;
} else if (isJMint) {
subType = TransactionSubType.join;
}
// at least one input was owned by this wallet
if (wasSentFromThisWallet) {
type = TransactionType.outgoing; type = TransactionType.outgoing;
amount = amountSentFromWallet - changeAmount - fee;
final possible = if (wasReceivedInThisWallet) {
outputAddresses.difference(myChangeReceivedOnAddresses).first; if (changeAmountReceivedInThisWallet + amountReceivedInThisWallet ==
totalOut) {
if (transactionAddress.value != possible) { // definitely sent all to self
transactionAddress = Address( type = TransactionType.sentToSelf;
walletId: walletId, } else if (amountReceivedInThisWallet == BigInt.zero) {
value: possible, // most likely just a typical send
derivationIndex: -1, // do nothing here yet
derivationPath: null,
subType: AddressSubType.nonWallet,
type: AddressType.nonWallet,
publicKey: [],
);
} }
} else { }
// incoming tx } else if (wasReceivedInThisWallet) {
// only found outputs owned by this wallet
type = TransactionType.incoming; 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, walletId: walletId,
txid: txObject["txid"] as String, blockHash: txData["blockhash"] as String?,
timestamp: txObject["blocktime"] as int? ?? hash: txData["hash"] as String,
(DateTime.now().millisecondsSinceEpoch ~/ 1000), 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, type: type,
subType: TransactionSubType.none, subType: subType,
// amount may overflow. Deprecated. Use amountString otherData: otherData,
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,
); );
txnsData.add(Tuple2(tx, transactionAddress)); txns.add(tx);
}
} }
await mainDB.addNewTransactionData(txnsData, walletId); await mainDB.updateOrPutTransactionV2s(txns);
} }
@override @override
@ -689,6 +551,7 @@ class FiroWallet extends Bip39HDWallet
await mainDB.deleteWalletBlockchainData(walletId); await mainDB.deleteWalletBlockchainData(walletId);
} }
// lelantus
final latestSetId = await electrumXClient.getLelantusLatestCoinId(); final latestSetId = await electrumXClient.getLelantusLatestCoinId();
final setDataMapFuture = getSetDataMap(latestSetId); final setDataMapFuture = getSetDataMap(latestSetId);
final usedSerialNumbersFuture = final usedSerialNumbersFuture =
@ -696,6 +559,17 @@ class FiroWallet extends Bip39HDWallet
coin: info.coin, 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 // receiving addresses
Logging.instance.log( Logging.instance.log(
"checking receiving addresses...", "checking receiving addresses...",
@ -799,16 +673,29 @@ class FiroWallet extends Bip39HDWallet
final futureResults = await Future.wait([ final futureResults = await Future.wait([
usedSerialNumbersFuture, usedSerialNumbersFuture,
setDataMapFuture, setDataMapFuture,
sparkAnonSetFuture,
sparkUsedCoinTagsFuture,
]); ]);
// lelantus
final usedSerialsSet = (futureResults[0] as List<String>).toSet(); final usedSerialsSet = (futureResults[0] as List<String>).toSet();
final setDataMap = futureResults[1] as Map<dynamic, dynamic>; 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, latestSetId: latestSetId,
usedSerialNumbers: usedSerialsSet, usedSerialNumbers: usedSerialsSet,
setDataMap: setDataMap, setDataMap: setDataMap,
); ),
recoverSparkWallet(
anonymitySet: sparkAnonymitySet,
spentCoinTags: sparkSpentCoinTags,
),
]);
}); });
await refresh(); await refresh();

View file

@ -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/lelantus_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/mnemonic_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/multi_address_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
abstract class Wallet<T extends CryptoCurrency> { abstract class Wallet<T extends CryptoCurrency> {
// default to Transaction class. For TransactionV2 set to 2 // default to Transaction class. For TransactionV2 set to 2
@ -270,7 +271,7 @@ abstract class Wallet<T extends CryptoCurrency> {
case Coin.firo: case Coin.firo:
return FiroWallet(CryptoCurrencyNetwork.main); return FiroWallet(CryptoCurrencyNetwork.main);
case Coin.firoTestNet: case Coin.firoTestNet:
return FiroWallet(CryptoCurrencyNetwork.main); return FiroWallet(CryptoCurrencyNetwork.test);
case Coin.nano: case Coin.nano:
return NanoWallet(CryptoCurrencyNetwork.main); 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 // listen to changes in db and updated wallet info property as required
void _watchWalletInfo() { void _watchWalletInfo() {
_walletInfoStream = mainDB.isar.walletInfo.watchObject(_walletInfo.id); _walletInfoStream = mainDB.isar.walletInfo.watchObject(
_walletInfo.id,
fireImmediately: true,
);
_walletInfoStream.forEach((element) { _walletInfoStream.forEach((element) {
if (element != null) { if (element != null) {
_walletInfo = element; _walletInfo = element;
@ -417,6 +421,10 @@ abstract class Wallet<T extends CryptoCurrency> {
.checkChangeAddressForTransactions(); .checkChangeAddressForTransactions();
} }
GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); 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)); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId));
final fetchFuture = updateTransactions(); final fetchFuture = updateTransactions();

View file

@ -625,9 +625,19 @@ mixin ElectrumXInterface on Bip39HDWallet {
// TODO: use coinlib // TODO: use coinlib
final txb = bitcoindart.TransactionBuilder( 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 // Add transaction inputs
for (var i = 0; i < utxoSigningData.length; i++) { for (var i = 0; i < utxoSigningData.length; i++) {
@ -1641,7 +1651,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
@override @override
Future<void> updateUTXOs() async { Future<void> updateUTXOs() async {
final allAddresses = await fetchAllOwnAddresses(); final allAddresses = await fetchAddressesForElectrumXScan();
try { try {
final fetchedUtxoList = <List<Map<String, dynamic>>>[]; final fetchedUtxoList = <List<Map<String, dynamic>>>[];
@ -1856,7 +1866,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
int estimateTxFee({required int vSize, required int feeRatePerKB}); int estimateTxFee({required int vSize, required int feeRatePerKB});
Amount roughFeeEstimate(int inputCount, int outputCount, 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 /// Certain coins need to check if the utxo should be marked
/// as blocked as well as give a reason. /// as blocked as well as give a reason.

View file

@ -1047,7 +1047,7 @@ mixin LelantusInterface on Bip39HDWallet, ElectrumXInterface {
return mints; return mints;
} }
Future<void> anonymizeAllPublicFunds() async { Future<void> anonymizeAllLelantus() async {
try { try {
final mintResult = await _mintSelection(); final mintResult = await _mintSelection();
@ -1056,7 +1056,7 @@ mixin LelantusInterface on Bip39HDWallet, ElectrumXInterface {
unawaited(refresh()); unawaited(refresh());
} catch (e, s) { } catch (e, s) {
Logging.instance.log( Logging.instance.log(
"Exception caught in anonymizeAllPublicFunds(): $e\n$s", "Exception caught in anonymizeAllLelantus(): $e\n$s",
level: LogLevel.Warning, level: LogLevel.Warning,
); );
rethrow; rethrow;

File diff suppressed because it is too large Load diff

View file

@ -16,6 +16,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
coinlib_flutter coinlib_flutter
flutter_libsparkmobile
tor_ffi_plugin tor_ffi_plugin
) )

View file

@ -1,4 +1,7 @@
PODS: PODS:
- coinlib_flutter (0.3.2):
- Flutter
- FlutterMacOS
- connectivity_plus (0.0.1): - connectivity_plus (0.0.1):
- FlutterMacOS - FlutterMacOS
- ReachabilitySwift - ReachabilitySwift
@ -22,14 +25,11 @@ PODS:
- cw_shared_external (0.0.1): - cw_shared_external (0.0.1):
- cw_shared_external/Boost (= 0.0.1) - cw_shared_external/Boost (= 0.0.1)
- cw_shared_external/OpenSSL (= 0.0.1) - cw_shared_external/OpenSSL (= 0.0.1)
- cw_shared_external/Sodium (= 0.0.1)
- FlutterMacOS - FlutterMacOS
- cw_shared_external/Boost (0.0.1): - cw_shared_external/Boost (0.0.1):
- FlutterMacOS - FlutterMacOS
- cw_shared_external/OpenSSL (0.0.1): - cw_shared_external/OpenSSL (0.0.1):
- FlutterMacOS - FlutterMacOS
- cw_shared_external/Sodium (0.0.1):
- FlutterMacOS
- cw_wownero (0.0.1): - cw_wownero (0.0.1):
- cw_wownero/Boost (= 0.0.1) - cw_wownero/Boost (= 0.0.1)
- cw_wownero/OpenSSL (= 0.0.1) - cw_wownero/OpenSSL (= 0.0.1)
@ -55,6 +55,8 @@ PODS:
- FlutterMacOS - FlutterMacOS
- flutter_libepiccash (0.0.1): - flutter_libepiccash (0.0.1):
- FlutterMacOS - FlutterMacOS
- flutter_libsparkmobile (0.0.1):
- FlutterMacOS
- flutter_local_notifications (0.0.1): - flutter_local_notifications (0.0.1):
- FlutterMacOS - FlutterMacOS
- flutter_secure_storage_macos (6.1.1): - flutter_secure_storage_macos (6.1.1):
@ -74,6 +76,7 @@ PODS:
- FlutterMacOS - FlutterMacOS
- stack_wallet_backup (0.0.1): - stack_wallet_backup (0.0.1):
- FlutterMacOS - FlutterMacOS
- tor_ffi_plugin (0.0.1)
- url_launcher_macos (0.0.1): - url_launcher_macos (0.0.1):
- FlutterMacOS - FlutterMacOS
- wakelock_macos (0.0.1): - wakelock_macos (0.0.1):
@ -82,6 +85,7 @@ PODS:
- FlutterMacOS - FlutterMacOS
DEPENDENCIES: DEPENDENCIES:
- coinlib_flutter (from `Flutter/ephemeral/.symlinks/plugins/coinlib_flutter/darwin`)
- connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`) - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`)
- cw_monero (from `Flutter/ephemeral/.symlinks/plugins/cw_monero/macos`) - cw_monero (from `Flutter/ephemeral/.symlinks/plugins/cw_monero/macos`)
- cw_shared_external (from `Flutter/ephemeral/.symlinks/plugins/cw_shared_external/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`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- devicelocale (from `Flutter/ephemeral/.symlinks/plugins/devicelocale/macos`) - devicelocale (from `Flutter/ephemeral/.symlinks/plugins/devicelocale/macos`)
- flutter_libepiccash (from `Flutter/ephemeral/.symlinks/plugins/flutter_libepiccash/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_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`) - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
- FlutterMacOS (from `Flutter/ephemeral`) - FlutterMacOS (from `Flutter/ephemeral`)
@ -99,6 +104,7 @@ DEPENDENCIES:
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
- stack_wallet_backup (from `Flutter/ephemeral/.symlinks/plugins/stack_wallet_backup/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`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- wakelock_macos (from `Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos`) - wakelock_macos (from `Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos`)
- window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`) - window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`)
@ -108,6 +114,8 @@ SPEC REPOS:
- ReachabilitySwift - ReachabilitySwift
EXTERNAL SOURCES: EXTERNAL SOURCES:
coinlib_flutter:
:path: Flutter/ephemeral/.symlinks/plugins/coinlib_flutter/darwin
connectivity_plus: connectivity_plus:
:path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
cw_monero: cw_monero:
@ -124,6 +132,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/devicelocale/macos :path: Flutter/ephemeral/.symlinks/plugins/devicelocale/macos
flutter_libepiccash: flutter_libepiccash:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_libepiccash/macos :path: Flutter/ephemeral/.symlinks/plugins/flutter_libepiccash/macos
flutter_libsparkmobile:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_libsparkmobile/macos
flutter_local_notifications: flutter_local_notifications:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos
flutter_secure_storage_macos: flutter_secure_storage_macos:
@ -142,6 +152,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
stack_wallet_backup: stack_wallet_backup:
:path: Flutter/ephemeral/.symlinks/plugins/stack_wallet_backup/macos :path: Flutter/ephemeral/.symlinks/plugins/stack_wallet_backup/macos
tor_ffi_plugin:
:path: Flutter/ephemeral/.symlinks/plugins/tor_ffi_plugin/macos
url_launcher_macos: url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
wakelock_macos: wakelock_macos:
@ -150,25 +162,28 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/window_size/macos :path: Flutter/ephemeral/.symlinks/plugins/window_size/macos
SPEC CHECKSUMS: SPEC CHECKSUMS:
coinlib_flutter: 6abec900d67762a6e7ccfd567a3cd3ae00bbee35
connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747 connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747
cw_monero: a3442556ad3c06365c912735e4a23942a28692b1 cw_monero: 7acce7238d217e3993ecac6ec2dec07be728769a
cw_shared_external: 1f631d1132521baac5f4caed43176fa10d4e0d8b cw_shared_external: c6adfd29c9be4d64f84e1fa9c541ccbcbdb6b457
cw_wownero: b4adb1e701fc363de27fa222fcaf4eff6f5fa63a cw_wownero: bcd7f2ad6c0a3e8e2a51756fb14f0579b6f8b4ff
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225 devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225
flutter_libepiccash: 9113ac75dd325f8bcf00bc3ab583c7fc2780cf3c flutter_libepiccash: be1560a04150c5cc85bcf08d236ec2b3d1f5d8da
flutter_libsparkmobile: 8ae86b0ccc7e52c9db6b53e258ee2977deb184ab
flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4 flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
isar_flutter_libs: 43385c99864c168fadba7c9adeddc5d38838ca6a isar_flutter_libs: 43385c99864c168fadba7c9adeddc5d38838ca6a
lelantus: 3dfbf92b1e66b3573494dfe3d6a21c4988b5361b lelantus: 308e42c5a648598936a07a234471dd8cf8e687a0
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7
stack_wallet_backup: 6ebc60b1bdcf11cf1f1cbad9aa78332e1e15778c stack_wallet_backup: 6ebc60b1bdcf11cf1f1cbad9aa78332e1e15778c
url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451 tor_ffi_plugin: 2566c1ed174688cca560fa0c64b7a799c66f07cb
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9 wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195 window_size: 339dafa0b27a95a62a843042038fa6c3c48de195

View file

@ -659,6 +659,15 @@ packages:
relative: true relative: true
source: path source: path
version: "0.0.1" 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: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:

View file

@ -27,6 +27,11 @@ dependencies:
lelantus: lelantus:
path: ./crypto_plugins/flutter_liblelantus path: ./crypto_plugins/flutter_liblelantus
flutter_libsparkmobile:
git:
url: https://github.com/cypherstack/flutter_libsparkmobile.git
ref: d54b4a1f492e48696c3df27eb8c31131a681cbc2
flutter_libmonero: flutter_libmonero:
path: ./crypto_plugins/flutter_libmonero path: ./crypto_plugins/flutter_libmonero
@ -160,7 +165,11 @@ dependencies:
url: https://github.com/cypherstack/tezart.git url: https://github.com/cypherstack/tezart.git
ref: 8a7070f533e63dd150edae99476f6853bfb25913 ref: 8a7070f533e63dd150edae99476f6853bfb25913
socks5_proxy: ^1.0.3+dev.3 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 convert: ^3.1.1
flutter_hooks: ^0.20.3 flutter_hooks: ^0.20.3
meta: ^1.9.1 meta: ^1.9.1

View file

@ -384,7 +384,7 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>); ) as _i5.Future<Map<String, dynamic>>);
@override @override
_i5.Future<Map<String, dynamic>> getSparkUsedCoinsTags({ _i5.Future<Set<String>> getSparkUsedCoinsTags({
String? requestID, String? requestID,
required int? startNumber, required int? startNumber,
}) => }) =>
@ -397,9 +397,8 @@ class MockElectrumXClient extends _i1.Mock implements _i4.ElectrumXClient {
#startNumber: startNumber, #startNumber: startNumber,
}, },
), ),
returnValue: returnValue: _i5.Future<Set<String>>.value(<String>{}),
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}), ) as _i5.Future<Set<String>>);
) as _i5.Future<Map<String, dynamic>>);
@override @override
_i5.Future<List<Map<String, dynamic>>> getSparkMintMetaData({ _i5.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
String? requestID, String? requestID,

View file

@ -74,6 +74,25 @@ class MockCachedElectrumXClient extends _i1.Mock
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>); ) as _i4.Future<Map<String, dynamic>>);
@override @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( String base64ToHex(String? source) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#base64ToHex, #base64ToHex,
@ -125,6 +144,16 @@ class MockCachedElectrumXClient extends _i1.Mock
returnValue: _i4.Future<List<String>>.value(<String>[]), returnValue: _i4.Future<List<String>>.value(<String>[]),
) as _i4.Future<List<String>>); ) as _i4.Future<List<String>>);
@override @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}) => _i4.Future<void> clearSharedTransactionCache({required _i5.Coin? coin}) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(

View file

@ -381,7 +381,7 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>); ) as _i4.Future<Map<String, dynamic>>);
@override @override
_i4.Future<Map<String, dynamic>> getSparkUsedCoinsTags({ _i4.Future<Set<String>> getSparkUsedCoinsTags({
String? requestID, String? requestID,
required int? startNumber, required int? startNumber,
}) => }) =>
@ -394,9 +394,8 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
#startNumber: startNumber, #startNumber: startNumber,
}, },
), ),
returnValue: returnValue: _i4.Future<Set<String>>.value(<String>{}),
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), ) as _i4.Future<Set<String>>);
) as _i4.Future<Map<String, dynamic>>);
@override @override
_i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({ _i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
String? requestID, String? requestID,
@ -516,6 +515,25 @@ class MockCachedElectrumXClient extends _i1.Mock
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>); ) as _i4.Future<Map<String, dynamic>>);
@override @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( String base64ToHex(String? source) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#base64ToHex, #base64ToHex,
@ -567,6 +585,16 @@ class MockCachedElectrumXClient extends _i1.Mock
returnValue: _i4.Future<List<String>>.value(<String>[]), returnValue: _i4.Future<List<String>>.value(<String>[]),
) as _i4.Future<List<String>>); ) as _i4.Future<List<String>>);
@override @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}) => _i4.Future<void> clearSharedTransactionCache({required _i6.Coin? coin}) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(

View file

@ -381,7 +381,7 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>); ) as _i4.Future<Map<String, dynamic>>);
@override @override
_i4.Future<Map<String, dynamic>> getSparkUsedCoinsTags({ _i4.Future<Set<String>> getSparkUsedCoinsTags({
String? requestID, String? requestID,
required int? startNumber, required int? startNumber,
}) => }) =>
@ -394,9 +394,8 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
#startNumber: startNumber, #startNumber: startNumber,
}, },
), ),
returnValue: returnValue: _i4.Future<Set<String>>.value(<String>{}),
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), ) as _i4.Future<Set<String>>);
) as _i4.Future<Map<String, dynamic>>);
@override @override
_i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({ _i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
String? requestID, String? requestID,
@ -516,6 +515,25 @@ class MockCachedElectrumXClient extends _i1.Mock
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>); ) as _i4.Future<Map<String, dynamic>>);
@override @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( String base64ToHex(String? source) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#base64ToHex, #base64ToHex,
@ -567,6 +585,16 @@ class MockCachedElectrumXClient extends _i1.Mock
returnValue: _i4.Future<List<String>>.value(<String>[]), returnValue: _i4.Future<List<String>>.value(<String>[]),
) as _i4.Future<List<String>>); ) as _i4.Future<List<String>>);
@override @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}) => _i4.Future<void> clearSharedTransactionCache({required _i6.Coin? coin}) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(

View file

@ -381,7 +381,7 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>); ) as _i4.Future<Map<String, dynamic>>);
@override @override
_i4.Future<Map<String, dynamic>> getSparkUsedCoinsTags({ _i4.Future<Set<String>> getSparkUsedCoinsTags({
String? requestID, String? requestID,
required int? startNumber, required int? startNumber,
}) => }) =>
@ -394,9 +394,8 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
#startNumber: startNumber, #startNumber: startNumber,
}, },
), ),
returnValue: returnValue: _i4.Future<Set<String>>.value(<String>{}),
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), ) as _i4.Future<Set<String>>);
) as _i4.Future<Map<String, dynamic>>);
@override @override
_i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({ _i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
String? requestID, String? requestID,
@ -516,6 +515,25 @@ class MockCachedElectrumXClient extends _i1.Mock
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>); ) as _i4.Future<Map<String, dynamic>>);
@override @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( String base64ToHex(String? source) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#base64ToHex, #base64ToHex,
@ -567,6 +585,16 @@ class MockCachedElectrumXClient extends _i1.Mock
returnValue: _i4.Future<List<String>>.value(<String>[]), returnValue: _i4.Future<List<String>>.value(<String>[]),
) as _i4.Future<List<String>>); ) as _i4.Future<List<String>>);
@override @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}) => _i4.Future<void> clearSharedTransactionCache({required _i6.Coin? coin}) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(

View file

@ -411,7 +411,7 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>); ) as _i5.Future<Map<String, dynamic>>);
@override @override
_i5.Future<Map<String, dynamic>> getSparkUsedCoinsTags({ _i5.Future<Set<String>> getSparkUsedCoinsTags({
String? requestID, String? requestID,
required int? startNumber, required int? startNumber,
}) => }) =>
@ -424,9 +424,8 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
#startNumber: startNumber, #startNumber: startNumber,
}, },
), ),
returnValue: returnValue: _i5.Future<Set<String>>.value(<String>{}),
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}), ) as _i5.Future<Set<String>>);
) as _i5.Future<Map<String, dynamic>>);
@override @override
_i5.Future<List<Map<String, dynamic>>> getSparkMintMetaData({ _i5.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
String? requestID, String? requestID,
@ -546,6 +545,25 @@ class MockCachedElectrumXClient extends _i1.Mock
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>); ) as _i5.Future<Map<String, dynamic>>);
@override @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( String base64ToHex(String? source) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#base64ToHex, #base64ToHex,
@ -597,6 +615,16 @@ class MockCachedElectrumXClient extends _i1.Mock
returnValue: _i5.Future<List<String>>.value(<String>[]), returnValue: _i5.Future<List<String>>.value(<String>[]),
) as _i5.Future<List<String>>); ) as _i5.Future<List<String>>);
@override @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}) => _i5.Future<void> clearSharedTransactionCache({required _i7.Coin? coin}) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(

View file

@ -381,7 +381,7 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>); ) as _i4.Future<Map<String, dynamic>>);
@override @override
_i4.Future<Map<String, dynamic>> getSparkUsedCoinsTags({ _i4.Future<Set<String>> getSparkUsedCoinsTags({
String? requestID, String? requestID,
required int? startNumber, required int? startNumber,
}) => }) =>
@ -394,9 +394,8 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
#startNumber: startNumber, #startNumber: startNumber,
}, },
), ),
returnValue: returnValue: _i4.Future<Set<String>>.value(<String>{}),
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), ) as _i4.Future<Set<String>>);
) as _i4.Future<Map<String, dynamic>>);
@override @override
_i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({ _i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
String? requestID, String? requestID,
@ -516,6 +515,25 @@ class MockCachedElectrumXClient extends _i1.Mock
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>); ) as _i4.Future<Map<String, dynamic>>);
@override @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( String base64ToHex(String? source) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#base64ToHex, #base64ToHex,
@ -567,6 +585,16 @@ class MockCachedElectrumXClient extends _i1.Mock
returnValue: _i4.Future<List<String>>.value(<String>[]), returnValue: _i4.Future<List<String>>.value(<String>[]),
) as _i4.Future<List<String>>); ) as _i4.Future<List<String>>);
@override @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}) => _i4.Future<void> clearSharedTransactionCache({required _i6.Coin? coin}) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(

View file

@ -381,7 +381,7 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>); ) as _i4.Future<Map<String, dynamic>>);
@override @override
_i4.Future<Map<String, dynamic>> getSparkUsedCoinsTags({ _i4.Future<Set<String>> getSparkUsedCoinsTags({
String? requestID, String? requestID,
required int? startNumber, required int? startNumber,
}) => }) =>
@ -394,9 +394,8 @@ class MockElectrumXClient extends _i1.Mock implements _i3.ElectrumXClient {
#startNumber: startNumber, #startNumber: startNumber,
}, },
), ),
returnValue: returnValue: _i4.Future<Set<String>>.value(<String>{}),
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), ) as _i4.Future<Set<String>>);
) as _i4.Future<Map<String, dynamic>>);
@override @override
_i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({ _i4.Future<List<Map<String, dynamic>>> getSparkMintMetaData({
String? requestID, String? requestID,
@ -516,6 +515,25 @@ class MockCachedElectrumXClient extends _i1.Mock
_i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}), _i4.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i4.Future<Map<String, dynamic>>); ) as _i4.Future<Map<String, dynamic>>);
@override @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( String base64ToHex(String? source) => (super.noSuchMethod(
Invocation.method( Invocation.method(
#base64ToHex, #base64ToHex,
@ -567,6 +585,16 @@ class MockCachedElectrumXClient extends _i1.Mock
returnValue: _i4.Future<List<String>>.value(<String>[]), returnValue: _i4.Future<List<String>>.value(<String>[]),
) as _i4.Future<List<String>>); ) as _i4.Future<List<String>>);
@override @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}) => _i4.Future<void> clearSharedTransactionCache({required _i6.Coin? coin}) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(

View file

@ -16,6 +16,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
flutter_libsparkmobile
tor_ffi_plugin tor_ffi_plugin
) )