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

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

View file

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

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) =>
base64Decode(LineSplitter.split(source).join())
.map((e) => e.toRadixString(16).padLeft(2, '0'))
@ -136,6 +193,7 @@ class CachedElectrumXClient {
result.remove("hex");
result.remove("lelantusData");
result.remove("sparkData");
if (result["confirmations"] != null &&
result["confirmations"] as int > minCacheConfirms) {
@ -198,14 +256,62 @@ class CachedElectrumXClient {
return resultingList;
} catch (e, s) {
Logging.instance.log(
"Failed to process CachedElectrumX.getTransaction(): $e\n$s",
level: LogLevel.Error);
"Failed to process CachedElectrumX.getUsedCoinSerials(): $e\n$s",
level: LogLevel.Error,
);
rethrow;
}
}
Future<Set<String>> getSparkUsedCoinsTags({
required Coin coin,
}) async {
try {
final box = await DB.instance.getSparkUsedCoinsTagsCacheBox(coin: coin);
final _list = box.get("tags") as List?;
Set<String> cachedTags =
_list == null ? {} : List<String>.from(_list).toSet();
final startNumber = max(
0,
cachedTags.length - 100, // 100 being some arbitrary buffer
);
final tags = await electrumXClient.getSparkUsedCoinsTags(
startNumber: startNumber,
);
// final newSerials = List<String>.from(serials["serials"] as List)
// .map((e) => !isHexadecimal(e) ? base64ToHex(e) : e)
// .toSet();
// ensure we are getting some overlap so we know we are not missing any
if (cachedTags.isNotEmpty && tags.isNotEmpty) {
assert(cachedTags.intersection(tags).isNotEmpty);
}
cachedTags.addAll(tags);
await box.put(
"tags",
cachedTags.toList(),
);
return cachedTags;
} catch (e, s) {
Logging.instance.log(
"Failed to process CachedElectrumX.getSparkUsedCoinsTags(): $e\n$s",
level: LogLevel.Error,
);
rethrow;
}
}
/// Clear all cached transactions for the specified coin
Future<void> clearSharedTransactionCache({required Coin coin}) async {
await DB.instance.clearSharedTransactionCache(coin: coin);
await DB.instance.closeAnonymitySetCacheBox(coin: coin);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:math';
import 'package:isar/isar.dart';
@ -6,6 +7,8 @@ import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart'
import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/extensions/extensions.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
part 'transaction_v2.g.dart';
@ -37,6 +40,8 @@ class TransactionV2 {
@enumerated
final TransactionSubType subType;
final String? otherData;
TransactionV2({
required this.walletId,
required this.blockHash,
@ -49,6 +54,7 @@ class TransactionV2 {
required this.version,
required this.type,
required this.subType,
required this.otherData,
});
int getConfirmations(int currentChainHeight) {
@ -71,7 +77,7 @@ class TransactionV2 {
return Amount(rawValue: inSum - outSum, fractionDigits: coin.decimals);
}
Amount getAmountReceivedThisWallet({required Coin coin}) {
Amount getAmountReceivedInThisWallet({required Coin coin}) {
final outSum = outputs
.where((e) => e.walletOwns)
.fold(BigInt.zero, (p, e) => p + e.value);
@ -79,12 +85,28 @@ class TransactionV2 {
return Amount(rawValue: outSum, fractionDigits: coin.decimals);
}
Amount getAmountSparkSelfMinted({required Coin coin}) {
final outSum = outputs.where((e) {
final op = e.scriptPubKeyHex.substring(0, 2).toUint8ListFromHex.first;
return e.walletOwns && (op == OP_SPARKMINT);
}).fold(BigInt.zero, (p, e) => p + e.value);
return Amount(rawValue: outSum, fractionDigits: coin.decimals);
}
Amount getAmountSentFromThisWallet({required Coin coin}) {
final inSum = inputs
.where((e) => e.walletOwns)
.fold(BigInt.zero, (p, e) => p + e.value);
return Amount(rawValue: inSum, fractionDigits: coin.decimals);
return Amount(
rawValue: inSum,
fractionDigits: coin.decimals,
) -
getAmountReceivedInThisWallet(
coin: coin,
) -
getFee(coin: coin);
}
Set<String> associatedAddresses() => {
@ -92,6 +114,82 @@ class TransactionV2 {
...outputs.map((e) => e.addresses).expand((e) => e),
};
Amount? getAnonFee() {
try {
final map = jsonDecode(otherData!) as Map;
return Amount.fromSerializedJsonString(map["anonFees"] as String);
} catch (_) {
return null;
}
}
String statusLabel({
required int currentChainHeight,
required int minConfirms,
}) {
if (subType == TransactionSubType.cashFusion ||
subType == TransactionSubType.mint ||
(subType == TransactionSubType.sparkMint &&
type == TransactionType.sentToSelf)) {
if (isConfirmed(currentChainHeight, minConfirms)) {
return "Anonymized";
} else {
return "Anonymizing";
}
}
// if (coin == Coin.epicCash) {
// if (_transaction.isCancelled) {
// return "Cancelled";
// } else if (type == TransactionType.incoming) {
// if (isConfirmed(height, minConfirms)) {
// return "Received";
// } else {
// if (_transaction.numberOfMessages == 1) {
// return "Receiving (waiting for sender)";
// } else if ((_transaction.numberOfMessages ?? 0) > 1) {
// return "Receiving (waiting for confirmations)"; // TODO test if the sender still has to open again after the receiver has 2 messages present, ie. sender->receiver->sender->node (yes) vs. sender->receiver->node (no)
// } else {
// return "Receiving";
// }
// }
// } else if (type == TransactionType.outgoing) {
// if (isConfirmed(height, minConfirms)) {
// return "Sent (confirmed)";
// } else {
// if (_transaction.numberOfMessages == 1) {
// return "Sending (waiting for receiver)";
// } else if ((_transaction.numberOfMessages ?? 0) > 1) {
// return "Sending (waiting for confirmations)";
// } else {
// return "Sending";
// }
// }
// }
// }
if (type == TransactionType.incoming) {
// if (_transaction.isMinting) {
// return "Minting";
// } else
if (isConfirmed(currentChainHeight, minConfirms)) {
return "Received";
} else {
return "Receiving";
}
} else if (type == TransactionType.outgoing) {
if (isConfirmed(currentChainHeight, minConfirms)) {
return "Sent";
} else {
return "Sending";
}
} else if (type == TransactionType.sentToSelf) {
return "Sent to self";
} else {
return type.name;
}
}
@override
String toString() {
return 'TransactionV2(\n'
@ -106,6 +204,7 @@ class TransactionV2 {
' version: $version,\n'
' inputs: $inputs,\n'
' outputs: $outputs,\n'
' otherData: $otherData,\n'
')';
}
}

View file

@ -38,41 +38,46 @@ const TransactionV2Schema = CollectionSchema(
type: IsarType.objectList,
target: r'InputV2',
),
r'outputs': PropertySchema(
r'otherData': PropertySchema(
id: 4,
name: r'otherData',
type: IsarType.string,
),
r'outputs': PropertySchema(
id: 5,
name: r'outputs',
type: IsarType.objectList,
target: r'OutputV2',
),
r'subType': PropertySchema(
id: 5,
id: 6,
name: r'subType',
type: IsarType.byte,
enumMap: _TransactionV2subTypeEnumValueMap,
),
r'timestamp': PropertySchema(
id: 6,
id: 7,
name: r'timestamp',
type: IsarType.long,
),
r'txid': PropertySchema(
id: 7,
id: 8,
name: r'txid',
type: IsarType.string,
),
r'type': PropertySchema(
id: 8,
id: 9,
name: r'type',
type: IsarType.byte,
enumMap: _TransactionV2typeEnumValueMap,
),
r'version': PropertySchema(
id: 9,
id: 10,
name: r'version',
type: IsarType.long,
),
r'walletId': PropertySchema(
id: 10,
id: 11,
name: r'walletId',
type: IsarType.string,
)
@ -161,6 +166,12 @@ int _transactionV2EstimateSize(
bytesCount += InputV2Schema.estimateSize(value, offsets, allOffsets);
}
}
{
final value = object.otherData;
if (value != null) {
bytesCount += 3 + value.length * 3;
}
}
bytesCount += 3 + object.outputs.length * 3;
{
final offsets = allOffsets[OutputV2]!;
@ -189,18 +200,19 @@ void _transactionV2Serialize(
InputV2Schema.serialize,
object.inputs,
);
writer.writeString(offsets[4], object.otherData);
writer.writeObjectList<OutputV2>(
offsets[4],
offsets[5],
allOffsets,
OutputV2Schema.serialize,
object.outputs,
);
writer.writeByte(offsets[5], object.subType.index);
writer.writeLong(offsets[6], object.timestamp);
writer.writeString(offsets[7], object.txid);
writer.writeByte(offsets[8], object.type.index);
writer.writeLong(offsets[9], object.version);
writer.writeString(offsets[10], object.walletId);
writer.writeByte(offsets[6], object.subType.index);
writer.writeLong(offsets[7], object.timestamp);
writer.writeString(offsets[8], object.txid);
writer.writeByte(offsets[9], object.type.index);
writer.writeLong(offsets[10], object.version);
writer.writeString(offsets[11], object.walletId);
}
TransactionV2 _transactionV2Deserialize(
@ -220,22 +232,23 @@ TransactionV2 _transactionV2Deserialize(
InputV2(),
) ??
[],
otherData: reader.readStringOrNull(offsets[4]),
outputs: reader.readObjectList<OutputV2>(
offsets[4],
offsets[5],
OutputV2Schema.deserialize,
allOffsets,
OutputV2(),
) ??
[],
subType:
_TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[5])] ??
_TransactionV2subTypeValueEnumMap[reader.readByteOrNull(offsets[6])] ??
TransactionSubType.none,
timestamp: reader.readLong(offsets[6]),
txid: reader.readString(offsets[7]),
type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[8])] ??
timestamp: reader.readLong(offsets[7]),
txid: reader.readString(offsets[8]),
type: _TransactionV2typeValueEnumMap[reader.readByteOrNull(offsets[9])] ??
TransactionType.outgoing,
version: reader.readLong(offsets[9]),
walletId: reader.readString(offsets[10]),
version: reader.readLong(offsets[10]),
walletId: reader.readString(offsets[11]),
);
object.id = id;
return object;
@ -263,6 +276,8 @@ P _transactionV2DeserializeProp<P>(
) ??
[]) as P;
case 4:
return (reader.readStringOrNull(offset)) as P;
case 5:
return (reader.readObjectList<OutputV2>(
offset,
OutputV2Schema.deserialize,
@ -270,20 +285,20 @@ P _transactionV2DeserializeProp<P>(
OutputV2(),
) ??
[]) as P;
case 5:
case 6:
return (_TransactionV2subTypeValueEnumMap[
reader.readByteOrNull(offset)] ??
TransactionSubType.none) as P;
case 6:
return (reader.readLong(offset)) as P;
case 7:
return (reader.readString(offset)) as P;
return (reader.readLong(offset)) as P;
case 8:
return (reader.readString(offset)) as P;
case 9:
return (_TransactionV2typeValueEnumMap[reader.readByteOrNull(offset)] ??
TransactionType.outgoing) as P;
case 9:
return (reader.readLong(offset)) as P;
case 10:
return (reader.readLong(offset)) as P;
case 11:
return (reader.readString(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
@ -297,6 +312,8 @@ const _TransactionV2subTypeEnumValueMap = {
'join': 3,
'ethToken': 4,
'cashFusion': 5,
'sparkMint': 6,
'sparkSpend': 7,
};
const _TransactionV2subTypeValueEnumMap = {
0: TransactionSubType.none,
@ -305,6 +322,8 @@ const _TransactionV2subTypeValueEnumMap = {
3: TransactionSubType.join,
4: TransactionSubType.ethToken,
5: TransactionSubType.cashFusion,
6: TransactionSubType.sparkMint,
7: TransactionSubType.sparkSpend,
};
const _TransactionV2typeEnumValueMap = {
'outgoing': 0,
@ -1244,6 +1263,160 @@ extension TransactionV2QueryFilter
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
otherDataIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'otherData',
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
otherDataIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'otherData',
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
otherDataEqualTo(
String? value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'otherData',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
otherDataGreaterThan(
String? value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'otherData',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
otherDataLessThan(
String? value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'otherData',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
otherDataBetween(
String? lower,
String? upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'otherData',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
otherDataStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'otherData',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
otherDataEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'otherData',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
otherDataContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'otherData',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
otherDataMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'otherData',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
otherDataIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'otherData',
value: '',
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
otherDataIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'otherData',
value: '',
));
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterFilterCondition>
outputsLengthEqualTo(int length) {
return QueryBuilder.apply(this, (query) {
@ -1887,6 +2060,19 @@ extension TransactionV2QuerySortBy
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortByOtherData() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'otherData', Sort.asc);
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy>
sortByOtherDataDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'otherData', Sort.desc);
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> sortBySubType() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'subType', Sort.asc);
@ -2013,6 +2199,19 @@ extension TransactionV2QuerySortThenBy
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenByOtherData() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'otherData', Sort.asc);
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy>
thenByOtherDataDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'otherData', Sort.desc);
});
}
QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> thenBySubType() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'subType', Sort.asc);
@ -2110,6 +2309,13 @@ extension TransactionV2QueryWhereDistinct
});
}
QueryBuilder<TransactionV2, TransactionV2, QDistinct> distinctByOtherData(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'otherData', caseSensitive: caseSensitive);
});
}
QueryBuilder<TransactionV2, TransactionV2, QDistinct> distinctBySubType() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'subType');
@ -2182,6 +2388,12 @@ extension TransactionV2QueryProperty
});
}
QueryBuilder<TransactionV2, String?, QQueryOperations> otherDataProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'otherData');
});
}
QueryBuilder<TransactionV2, List<OutputV2>, QQueryOperations>
outputsProperty() {
return QueryBuilder.apply(this, (query) {

View file

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

View file

@ -352,6 +352,16 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
data: address.derivationPath!.value,
button: Container(),
),
if (address.type == AddressType.spark)
const _Div(
height: 12,
),
if (address.type == AddressType.spark)
_Item(
title: "Diversifier",
data: address.derivationIndex.toString(),
button: Container(),
),
const _Div(
height: 12,
),

View file

@ -10,15 +10,18 @@
import 'dart:async';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:isar/isar.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/receive_view/addresses/wallet_addresses_view.dart';
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/themes/stack_colors.dart';
@ -30,7 +33,9 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
@ -58,6 +63,11 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
late final Coin coin;
late final String walletId;
late final ClipboardInterface clipboard;
late final bool supportsSpark;
String? _sparkAddress;
String? _qrcodeContent;
bool _showSparkAddress = true;
Future<void> generateNewAddress() async {
final wallet = ref.read(pWallets).getWallet(walletId);
@ -96,23 +106,106 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
}
}
Future<void> generateNewSparkAddress() async {
final wallet = ref.read(pWallets).getWallet(walletId);
if (wallet is SparkInterface) {
bool shouldPop = false;
unawaited(
showDialog(
context: context,
builder: (_) {
return WillPopScope(
onWillPop: () async => shouldPop,
child: Container(
color: Theme.of(context)
.extension<StackColors>()!
.overlay
.withOpacity(0.5),
child: const CustomLoadingOverlay(
message: "Generating address",
eventBus: null,
),
),
);
},
),
);
final address = await wallet.generateNextSparkAddress();
await ref.read(mainDBProvider).isar.writeTxn(() async {
await ref.read(mainDBProvider).isar.addresses.put(address);
});
shouldPop = true;
if (mounted) {
Navigator.of(context, rootNavigator: true).pop();
if (_sparkAddress != address.value) {
setState(() {
_sparkAddress = address.value;
});
}
}
}
}
StreamSubscription<Address?>? _streamSub;
@override
void initState() {
walletId = widget.walletId;
coin = ref.read(pWalletCoin(walletId));
clipboard = widget.clipboard;
supportsSpark = ref.read(pWallets).getWallet(walletId) is SparkInterface;
if (supportsSpark) {
_streamSub = ref
.read(mainDBProvider)
.isar
.addresses
.where()
.walletIdEqualTo(walletId)
.filter()
.typeEqualTo(AddressType.spark)
.sortByDerivationIndexDesc()
.findFirst()
.asStream()
.listen((event) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
_sparkAddress = event?.value;
});
}
});
});
}
super.initState();
}
@override
void dispose() {
_streamSub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
final receivingAddress = ref.watch(pWalletReceivingAddress(walletId));
final ticker = widget.tokenContract?.symbol ?? coin.ticker;
if (supportsSpark) {
if (_showSparkAddress) {
_qrcodeContent = _sparkAddress;
} else {
_qrcodeContent = ref.watch(pWalletReceivingAddress(walletId));
}
} else {
_qrcodeContent = ref.watch(pWalletReceivingAddress(walletId));
}
return Background(
child: Scaffold(
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
@ -225,86 +318,239 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
GestureDetector(
onTap: () {
HapticFeedback.lightImpact();
clipboard.setData(
ClipboardData(text: receivingAddress),
);
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
);
},
child: RoundedWhiteContainer(
child: Column(
children: [
Row(
children: [
Text(
"Your $ticker address",
style: STextStyles.itemSubtitle(context),
),
const Spacer(),
Row(
children: [
SvgPicture.asset(
Assets.svg.copy,
width: 10,
height: 10,
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
),
const SizedBox(
width: 4,
),
Text(
"Copy",
style: STextStyles.link2(context),
),
],
),
],
),
const SizedBox(
height: 4,
),
Row(
children: [
Expanded(
ConditionalParent(
condition: supportsSpark,
builder: (child) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
DropdownButtonHideUnderline(
child: DropdownButton2<bool>(
value: _showSparkAddress,
items: [
DropdownMenuItem(
value: true,
child: Text(
receivingAddress,
style: STextStyles.itemSubtitle12(context),
"Spark address",
style: STextStyles.desktopTextMedium(context),
),
),
DropdownMenuItem(
value: false,
child: Text(
"Transparent address",
style: STextStyles.desktopTextMedium(context),
),
),
],
onChanged: (value) {
if (value is bool && value != _showSparkAddress) {
setState(() {
_showSparkAddress = value;
});
}
},
isExpanded: true,
iconStyleData: IconStyleData(
icon: Padding(
padding: const EdgeInsets.only(right: 10),
child: SvgPicture.asset(
Assets.svg.chevronDown,
width: 12,
height: 6,
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
),
),
),
dropdownStyleData: DropdownStyleData(
offset: const Offset(0, -10),
elevation: 0,
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
),
menuItemStyleData: const MenuItemStyleData(
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
),
),
],
),
const SizedBox(
height: 12,
),
if (_showSparkAddress)
GestureDetector(
onTap: () {
clipboard.setData(
ClipboardData(text: _sparkAddress ?? "Error"),
);
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
);
},
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context)
.extension<StackColors>()!
.backgroundAppBar,
width: 1,
),
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
child: RoundedWhiteContainer(
child: Column(
children: [
Row(
children: [
Text(
"Your ${coin.ticker} SPARK address",
style:
STextStyles.itemSubtitle(context),
),
const Spacer(),
Row(
children: [
SvgPicture.asset(
Assets.svg.copy,
width: 15,
height: 15,
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
),
const SizedBox(
width: 4,
),
Text(
"Copy",
style: STextStyles.link2(context),
),
],
),
],
),
const SizedBox(
height: 8,
),
Row(
children: [
Expanded(
child: Text(
_sparkAddress ?? "Error",
style: STextStyles
.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
),
],
),
],
),
),
),
),
if (!_showSparkAddress) child,
],
),
child: GestureDetector(
onTap: () {
HapticFeedback.lightImpact();
clipboard.setData(
ClipboardData(
text:
ref.watch(pWalletReceivingAddress(walletId))),
);
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
);
},
child: RoundedWhiteContainer(
child: Column(
children: [
Row(
children: [
Text(
"Your $ticker address",
style: STextStyles.itemSubtitle(context),
),
const Spacer(),
Row(
children: [
SvgPicture.asset(
Assets.svg.copy,
width: 10,
height: 10,
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
),
const SizedBox(
width: 4,
),
Text(
"Copy",
style: STextStyles.link2(context),
),
],
),
],
),
const SizedBox(
height: 4,
),
Row(
children: [
Expanded(
child: Text(
ref.watch(
pWalletReceivingAddress(walletId)),
style: STextStyles.itemSubtitle12(context),
),
),
],
),
],
),
),
),
),
if (coin != Coin.epicCash &&
coin != Coin.ethereum &&
coin != Coin.banano &&
coin != Coin.nano &&
coin != Coin.stellar &&
coin != Coin.stellarTestnet &&
coin != Coin.tezos)
if (ref.watch(pWallets
.select((value) => value.getWallet(walletId)))
is MultiAddressInterface ||
supportsSpark)
const SizedBox(
height: 12,
),
if (coin != Coin.epicCash &&
coin != Coin.ethereum &&
coin != Coin.banano &&
coin != Coin.nano &&
coin != Coin.stellar &&
coin != Coin.stellarTestnet &&
coin != Coin.tezos)
if (ref.watch(pWallets
.select((value) => value.getWallet(walletId)))
is MultiAddressInterface ||
supportsSpark)
TextButton(
onPressed: generateNewAddress,
onPressed: supportsSpark && _showSparkAddress
? generateNewSparkAddress
: generateNewAddress,
style: Theme.of(context)
.extension<StackColors>()!
.getSecondaryEnabledButtonStyle(context),
@ -328,7 +574,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
QrImageView(
data: AddressUtils.buildUriString(
coin,
receivingAddress,
_qrcodeContent ?? "",
{},
),
size: MediaQuery.of(context).size.width / 2,
@ -347,7 +593,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
RouteGenerator.useMaterialPageRoute,
builder: (_) => GenerateUriQrCodeView(
coin: coin,
receivingAddress: receivingAddress,
receivingAddress: _qrcodeContent ?? "",
),
settings: const RouteSettings(
name: GenerateUriQrCodeView.routeName,

View file

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

File diff suppressed because it is too large Load diff

View file

@ -101,9 +101,9 @@ class _FiroBalanceSelectionSheetState
onTap: () {
final state =
ref.read(publicPrivateBalanceStateProvider.state).state;
if (state != "Private") {
if (state != FiroType.spark) {
ref.read(publicPrivateBalanceStateProvider.state).state =
"Private";
FiroType.spark;
}
Navigator.of(context).pop();
},
@ -122,7 +122,7 @@ class _FiroBalanceSelectionSheetState
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: "Private",
value: FiroType.spark,
groupValue: ref
.watch(
publicPrivateBalanceStateProvider.state)
@ -131,7 +131,7 @@ class _FiroBalanceSelectionSheetState
ref
.read(publicPrivateBalanceStateProvider
.state)
.state = "Private";
.state = FiroType.spark;
Navigator.of(context).pop();
},
@ -149,7 +149,86 @@ class _FiroBalanceSelectionSheetState
// Row(
// children: [
Text(
"Private balance",
"Spark balance",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
const SizedBox(
width: 2,
),
Text(
ref.watch(pAmountFormatter(coin)).format(
firoWallet
.info.cachedBalanceTertiary.spendable,
),
style: STextStyles.itemSubtitle(context),
textAlign: TextAlign.left,
),
],
),
// ],
// ),
)
],
),
),
),
const SizedBox(
height: 16,
),
GestureDetector(
onTap: () {
final state =
ref.read(publicPrivateBalanceStateProvider.state).state;
if (state != FiroType.lelantus) {
ref.read(publicPrivateBalanceStateProvider.state).state =
FiroType.lelantus;
}
Navigator.of(context).pop();
},
child: Container(
color: Colors.transparent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
width: 20,
height: 20,
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: FiroType.lelantus,
groupValue: ref
.watch(
publicPrivateBalanceStateProvider.state)
.state,
onChanged: (x) {
ref
.read(publicPrivateBalanceStateProvider
.state)
.state = FiroType.lelantus;
Navigator.of(context).pop();
},
),
),
],
),
const SizedBox(
width: 12,
),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Row(
// children: [
Text(
"Lelantus balance",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
@ -180,9 +259,9 @@ class _FiroBalanceSelectionSheetState
onTap: () {
final state =
ref.read(publicPrivateBalanceStateProvider.state).state;
if (state != "Public") {
if (state != FiroType.public) {
ref.read(publicPrivateBalanceStateProvider.state).state =
"Public";
FiroType.public;
}
Navigator.of(context).pop();
},
@ -200,7 +279,7 @@ class _FiroBalanceSelectionSheetState
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: "Public",
value: FiroType.public,
groupValue: ref
.watch(
publicPrivateBalanceStateProvider.state)
@ -209,7 +288,7 @@ class _FiroBalanceSelectionSheetState
ref
.read(publicPrivateBalanceStateProvider
.state)
.state = "Public";
.state = FiroType.public;
Navigator.of(context).pop();
},
),

View file

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

View file

@ -15,14 +15,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/electrumx_rpc/electrumx_client.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/providers/global/debug_service_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/background.dart';
@ -217,98 +215,7 @@ class HiddenSettings extends StatelessWidget {
),
);
}),
// const SizedBox(
// height: 12,
// ),
// Consumer(builder: (_, ref, __) {
// return GestureDetector(
// onTap: () async {
// final x =
// await MajesticBankAPI.instance.getRates();
// print(x);
// },
// child: RoundedWhiteContainer(
// child: Text(
// "Click me",
// style: STextStyles.button(context).copyWith(
// color: Theme.of(context)
// .extension<StackColors>()!
// .accentColorDark),
// ),
// ),
// );
// }),
// const SizedBox(
// height: 12,
// ),
// Consumer(builder: (_, ref, __) {
// return GestureDetector(
// onTap: () async {
// ref
// .read(priceAnd24hChangeNotifierProvider)
// .tokenContractAddressesToCheck
// .add(
// "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
// ref
// .read(priceAnd24hChangeNotifierProvider)
// .tokenContractAddressesToCheck
// .add(
// "0xdAC17F958D2ee523a2206206994597C13D831ec7");
// await ref
// .read(priceAnd24hChangeNotifierProvider)
// .updatePrice();
//
// final x = ref
// .read(priceAnd24hChangeNotifierProvider)
// .getTokenPrice(
// "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
//
// print(
// "PRICE 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48: $x");
// },
// child: RoundedWhiteContainer(
// child: Text(
// "Click me",
// style: STextStyles.button(context).copyWith(
// color: Theme.of(context)
// .extension<StackColors>()!
// .accentColorDark),
// ),
// ),
// );
// }),
// const SizedBox(
// height: 12,
// ),
// Consumer(builder: (_, ref, __) {
// return GestureDetector(
// onTap: () async {
// // final erc20 = Erc20ContractInfo(
// // contractAddress: 'some con',
// // name: "loonamsn",
// // symbol: "DD",
// // decimals: 19,
// // );
// //
// // final json = erc20.toJson();
// //
// // print(json);
// //
// // final ee = EthContractInfo.fromJson(json);
// //
// // print(ee);
// },
// child: RoundedWhiteContainer(
// child: Text(
// "Click me",
// style: STextStyles.button(context).copyWith(
// color: Theme.of(context)
// .extension<StackColors>()!
// .accentColorDark),
// ),
// ),
// );
// }),
const SizedBox(
height: 12,
),
@ -345,9 +252,6 @@ class HiddenSettings extends StatelessWidget {
}
},
),
const SizedBox(
height: 12,
),
Consumer(
builder: (_, ref, __) {
return GestureDetector(
@ -366,221 +270,6 @@ class HiddenSettings extends StatelessWidget {
);
},
),
const SizedBox(
height: 12,
),
Consumer(
builder: (_, ref, __) {
return GestureDetector(
onTap: () async {
try {
final n = DefaultNodes.firoTestnet;
final e = ElectrumXClient.from(
node: ElectrumXNode(
address: n.host,
port: n.port,
name: n.name,
id: n.id,
useSSL: n.useSSL,
),
prefs:
ref.read(prefsChangeNotifierProvider),
failovers: [],
);
// Call and print getSparkAnonymitySet.
final anonymitySet =
await e.getSparkAnonymitySet(
coinGroupId: "1",
startBlockHash: "",
);
Util.printJson(anonymitySet, "anonymitySet");
} catch (e, s) {
print("$e\n$s");
}
},
child: RoundedWhiteContainer(
child: Text(
"Spark getSparkAnonymitySet",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
);
},
),
const SizedBox(
height: 12,
),
Consumer(
builder: (_, ref, __) {
return GestureDetector(
onTap: () async {
try {
final n = DefaultNodes.firoTestnet;
final e = ElectrumXClient.from(
node: ElectrumXNode(
address: n.host,
port: n.port,
name: n.name,
id: n.id,
useSSL: n.useSSL,
),
prefs:
ref.read(prefsChangeNotifierProvider),
failovers: [],
);
// Call and print getUsedCoinsTags.
final usedCoinsTags = await e
.getSparkUsedCoinsTags(startNumber: 0);
print(
"usedCoinsTags['tags'].length: ${usedCoinsTags["tags"].length}");
Util.printJson(
usedCoinsTags, "usedCoinsTags");
} catch (e, s) {
print("$e\n$s");
}
},
child: RoundedWhiteContainer(
child: Text(
"Spark getSparkUsedCoinsTags",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
);
},
),
const SizedBox(
height: 12,
),
Consumer(
builder: (_, ref, __) {
return GestureDetector(
onTap: () async {
try {
final n = DefaultNodes.firoTestnet;
final e = ElectrumXClient.from(
node: ElectrumXNode(
address: n.host,
port: n.port,
name: n.name,
id: n.id,
useSSL: n.useSSL,
),
prefs:
ref.read(prefsChangeNotifierProvider),
failovers: [],
);
// Call and print getSparkMintMetaData.
final mintMetaData =
await e.getSparkMintMetaData(
sparkCoinHashes: [
"b476ed2b374bb081ea51d111f68f0136252521214e213d119b8dc67b92f5a390",
],
);
Util.printJson(mintMetaData, "mintMetaData");
} catch (e, s) {
print("$e\n$s");
}
},
child: RoundedWhiteContainer(
child: Text(
"Spark getSparkMintMetaData",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
);
},
),
const SizedBox(
height: 12,
),
Consumer(
builder: (_, ref, __) {
return GestureDetector(
onTap: () async {
try {
final n = DefaultNodes.firoTestnet;
final e = ElectrumXClient.from(
node: ElectrumXNode(
address: n.host,
port: n.port,
name: n.name,
id: n.id,
useSSL: n.useSSL,
),
prefs:
ref.read(prefsChangeNotifierProvider),
failovers: [],
);
// Call and print getSparkLatestCoinId.
final latestCoinId =
await e.getSparkLatestCoinId();
Util.printJson(latestCoinId, "latestCoinId");
} catch (e, s) {
print("$e\n$s");
}
},
child: RoundedWhiteContainer(
child: Text(
"Spark getSparkLatestCoinId",
style: STextStyles.button(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark),
),
),
);
},
),
// const SizedBox(
// height: 12,
// ),
// GestureDetector(
// onTap: () async {
// showDialog<void>(
// context: context,
// builder: (_) {
// return StackDialogBase(
// child: SizedBox(
// width: 300,
// child: Lottie.asset(
// Assets.lottie.plain(Coin.bitcoincash),
// ),
// ),
// );
// },
// );
// },
// child: RoundedWhiteContainer(
// child: Text(
// "Lottie test",
// style: STextStyles.button(context).copyWith(
// color: Theme.of(context)
// .extension<StackColors>()!
// .accentColorDark),
// ),
// ),
// ),
],
),
),

View file

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

View file

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

View file

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

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

View file

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

View file

@ -95,7 +95,12 @@ class _TransactionV2DetailsViewState
minConfirms =
ref.read(pWallets).getWallet(walletId).cryptoCurrency.minConfirms;
fee = _transaction.getFee(coin: coin);
if (_transaction.subType == TransactionSubType.join ||
_transaction.subType == TransactionSubType.sparkSpend) {
fee = _transaction.getAnonFee()!;
} else {
fee = _transaction.getFee(coin: coin);
}
if (_transaction.subType == TransactionSubType.cashFusion ||
_transaction.type == TransactionType.sentToSelf) {
@ -107,7 +112,7 @@ class _TransactionV2DetailsViewState
unit = coin.ticker;
if (_transaction.subType == TransactionSubType.cashFusion) {
amount = _transaction.getAmountReceivedThisWallet(coin: coin);
amount = _transaction.getAmountReceivedInThisWallet(coin: coin);
data = _transaction.outputs
.where((e) => e.walletOwns)
.map((e) => (
@ -131,7 +136,11 @@ class _TransactionV2DetailsViewState
case TransactionType.incoming:
case TransactionType.sentToSelf:
amount = _transaction.getAmountReceivedThisWallet(coin: coin);
if (_transaction.subType == TransactionSubType.sparkMint) {
amount = _transaction.getAmountSparkSelfMinted(coin: coin);
} else {
amount = _transaction.getAmountReceivedInThisWallet(coin: coin);
}
data = _transaction.outputs
.where((e) => e.walletOwns)
.map((e) => (
@ -164,77 +173,10 @@ class _TransactionV2DetailsViewState
super.dispose();
}
String whatIsIt(TransactionV2 tx, int height) {
final type = tx.type;
if (coin == Coin.firo || coin == Coin.firoTestNet) {
if (tx.subType == TransactionSubType.mint) {
if (tx.isConfirmed(height, minConfirms)) {
return "Minted";
} else {
return "Minting";
}
}
}
// if (coin == Coin.epicCash) {
// if (_transaction.isCancelled) {
// return "Cancelled";
// } else if (type == TransactionType.incoming) {
// if (tx.isConfirmed(height, minConfirms)) {
// return "Received";
// } else {
// if (_transaction.numberOfMessages == 1) {
// return "Receiving (waiting for sender)";
// } else if ((_transaction.numberOfMessages ?? 0) > 1) {
// return "Receiving (waiting for confirmations)"; // TODO test if the sender still has to open again after the receiver has 2 messages present, ie. sender->receiver->sender->node (yes) vs. sender->receiver->node (no)
// } else {
// return "Receiving";
// }
// }
// } else if (type == TransactionType.outgoing) {
// if (tx.isConfirmed(height, minConfirms)) {
// return "Sent (confirmed)";
// } else {
// if (_transaction.numberOfMessages == 1) {
// return "Sending (waiting for receiver)";
// } else if ((_transaction.numberOfMessages ?? 0) > 1) {
// return "Sending (waiting for confirmations)";
// } else {
// return "Sending";
// }
// }
// }
// }
if (tx.subType == TransactionSubType.cashFusion) {
if (tx.isConfirmed(height, minConfirms)) {
return "Anonymized";
} else {
return "Anonymizing";
}
}
if (type == TransactionType.incoming) {
// if (_transaction.isMinting) {
// return "Minting";
// } else
if (tx.isConfirmed(height, minConfirms)) {
return "Received";
} else {
return "Receiving";
}
} else if (type == TransactionType.outgoing) {
if (tx.isConfirmed(height, minConfirms)) {
return "Sent";
} else {
return "Sending";
}
} else if (type == TransactionType.sentToSelf) {
return "Sent to self";
} else {
return type.name;
}
}
String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel(
currentChainHeight: height,
minConfirms: minConfirms,
);
Future<String> fetchContactNameFor(String address) async {
if (address.isEmpty) {

View file

@ -8,6 +8,8 @@
*
*/
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
@ -40,6 +42,9 @@ class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
bool _hasLoaded = false;
List<TransactionV2> _transactions = [];
late final StreamSubscription<List<TransactionV2>> _subscription;
late final QueryBuilder<TransactionV2, TransactionV2, QAfterSortBy> _query;
BorderRadius get _borderRadiusFirst {
return BorderRadius.only(
topLeft: Radius.circular(
@ -62,19 +67,39 @@ class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
);
}
@override
void initState() {
_query = ref
.read(mainDBProvider)
.isar
.transactionV2s
.where()
.walletIdEqualTo(widget.walletId)
.sortByTimestampDesc();
_subscription = _query.watch().listen((event) {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_transactions = event;
});
});
});
super.initState();
}
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final coin = ref.watch(pWallets).getWallet(widget.walletId).info.coin;
return FutureBuilder(
future: ref
.watch(mainDBProvider)
.isar
.transactionV2s
.where()
.walletIdEqualTo(widget.walletId)
.sortByTimestampDesc()
.findAll(),
future: _query.findAll(),
builder: (fbContext, AsyncSnapshot<List<TransactionV2>> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
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/unread_notifications_provider.dart';
import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart';
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart';
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
@ -63,7 +61,6 @@ import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/backup_frequency_type.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/text_styles.dart';
@ -71,6 +68,7 @@ import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
@ -118,6 +116,8 @@ class _WalletViewState extends ConsumerState<WalletView> {
late final String walletId;
late final Coin coin;
late final bool isSparkWallet;
late final bool _shouldDisableAutoSyncOnLogOut;
late WalletSyncStatus _currentSyncStatus;
@ -174,6 +174,8 @@ class _WalletViewState extends ConsumerState<WalletView> {
_shouldDisableAutoSyncOnLogOut = false;
}
isSparkWallet = wallet is SparkInterface;
if (coin == Coin.firo &&
(wallet as FiroWallet).lelantusCoinIsarRescanRequired) {
_rescanningOnOpen = true;
@ -433,7 +435,8 @@ class _WalletViewState extends ConsumerState<WalletView> {
}
try {
await firoWallet.anonymizeAllPublicFunds();
// await firoWallet.anonymizeAllLelantus();
await firoWallet.anonymizeAllSpark();
shouldPop = true;
if (mounted) {
Navigator.of(context).popUntil(
@ -760,11 +763,11 @@ class _WalletViewState extends ConsumerState<WalletView> {
),
),
),
if (coin == Coin.firo)
if (isSparkWallet)
const SizedBox(
height: 10,
),
if (coin == Coin.firo)
if (isSparkWallet)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
@ -951,20 +954,21 @@ class _WalletViewState extends ConsumerState<WalletView> {
label: "Send",
icon: const SendNavIcon(),
onTap: () {
switch (ref
.read(walletBalanceToggleStateProvider.state)
.state) {
case WalletBalanceToggleState.full:
ref
.read(publicPrivateBalanceStateProvider.state)
.state = "Public";
break;
case WalletBalanceToggleState.available:
ref
.read(publicPrivateBalanceStateProvider.state)
.state = "Private";
break;
}
// not sure what this is supposed to accomplish?
// switch (ref
// .read(walletBalanceToggleStateProvider.state)
// .state) {
// case WalletBalanceToggleState.full:
// ref
// .read(publicPrivateBalanceStateProvider.state)
// .state = "Public";
// break;
// case WalletBalanceToggleState.available:
// ref
// .read(publicPrivateBalanceStateProvider.state)
// .state = "Private";
// break;
// }
Navigator.of(context).pushNamed(
SendView.routeName,
arguments: Tuple2(

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

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

View file

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

View file

@ -10,14 +10,18 @@
import 'dart:async';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:isar/isar.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/themes/stack_colors.dart';
@ -29,7 +33,9 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart';
import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
@ -57,10 +63,15 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
late final Coin coin;
late final String walletId;
late final ClipboardInterface clipboard;
late final bool supportsSpark;
String? _sparkAddress;
String? _qrcodeContent;
bool _showSparkAddress = true;
Future<void> generateNewAddress() async {
final wallet = ref.read(pWallets).getWallet(walletId);
if (wallet is Bip39HDWallet) {
if (wallet is MultiAddressInterface) {
bool shouldPop = false;
unawaited(
showDialog(
@ -93,130 +104,370 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
}
}
Future<void> generateNewSparkAddress() async {
final wallet = ref.read(pWallets).getWallet(walletId);
if (wallet is SparkInterface) {
bool shouldPop = false;
unawaited(
showDialog(
context: context,
builder: (_) {
return WillPopScope(
onWillPop: () async => shouldPop,
child: Container(
color: Theme.of(context)
.extension<StackColors>()!
.overlay
.withOpacity(0.5),
child: const CustomLoadingOverlay(
message: "Generating address",
eventBus: null,
),
),
);
},
),
);
final address = await wallet.generateNextSparkAddress();
await ref.read(mainDBProvider).isar.writeTxn(() async {
await ref.read(mainDBProvider).isar.addresses.put(address);
});
shouldPop = true;
if (mounted) {
Navigator.of(context, rootNavigator: true).pop();
if (_sparkAddress != address.value) {
setState(() {
_sparkAddress = address.value;
});
}
}
}
}
StreamSubscription<Address?>? _streamSub;
@override
void initState() {
walletId = widget.walletId;
coin = ref.read(pWalletInfo(walletId)).coin;
clipboard = widget.clipboard;
supportsSpark = ref.read(pWallets).getWallet(walletId) is SparkInterface;
if (supportsSpark) {
_streamSub = ref
.read(mainDBProvider)
.isar
.addresses
.where()
.walletIdEqualTo(walletId)
.filter()
.typeEqualTo(AddressType.spark)
.sortByDerivationIndexDesc()
.findFirst()
.asStream()
.listen((event) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
_sparkAddress = event?.value;
});
}
});
});
}
super.initState();
}
@override
void dispose() {
_streamSub?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
debugPrint("BUILD: $runtimeType");
final receivingAddress = ref.watch(pWalletReceivingAddress(walletId));
if (supportsSpark) {
if (_showSparkAddress) {
_qrcodeContent = _sparkAddress;
} else {
_qrcodeContent = ref.watch(pWalletReceivingAddress(walletId));
}
} else {
_qrcodeContent = ref.watch(pWalletReceivingAddress(walletId));
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
clipboard.setData(
ClipboardData(text: receivingAddress),
);
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,
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,
),
),
),
),
child: RoundedWhiteContainer(
child: Column(
children: [
Row(
children: [
Text(
"Your ${widget.contractAddress == null ? coin.ticker : ref.watch(
tokenServiceProvider.select(
(value) => value!.tokenContract.symbol,
),
)} address",
style: STextStyles.itemSubtitle(context),
const SizedBox(
height: 12,
),
if (_showSparkAddress)
MouseRegion(
cursor: SystemMouseCursors.click,
child: 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,
),
const Spacer(),
Row(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
child: RoundedWhiteContainer(
child: Column(
children: [
SvgPicture.asset(
Assets.svg.copy,
width: 15,
height: 15,
color: Theme.of(context)
.extension<StackColors>()!
.infoItemIcons,
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(
width: 4,
height: 8,
),
Text(
"Copy",
style: STextStyles.link2(context),
Row(
children: [
Expanded(
child: Text(
_sparkAddress ?? "Error",
style:
STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
),
],
),
],
),
],
),
),
const SizedBox(
height: 8,
),
Row(
children: [
Expanded(
child: Text(
receivingAddress,
style:
STextStyles.desktopTextExtraExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
if (!_showSparkAddress) child,
],
),
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
clipboard.setData(
ClipboardData(
text: ref.watch(pWalletReceivingAddress(walletId))),
);
showFloatingFlushBar(
type: FlushBarType.info,
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,
),
)} 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(
ref.watch(pWalletReceivingAddress(walletId)),
style: STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
),
),
],
),
],
],
),
],
),
),
),
),
),
),
if (coin != Coin.epicCash &&
coin != Coin.ethereum &&
coin != Coin.banano &&
coin != Coin.nano &&
coin != Coin.stellar &&
coin != Coin.stellarTestnet &&
coin != Coin.tezos)
if (ref.watch(pWallets.select((value) => value.getWallet(walletId)))
is MultiAddressInterface ||
supportsSpark)
const SizedBox(
height: 20,
),
if (coin != Coin.epicCash &&
coin != Coin.ethereum &&
coin != Coin.banano &&
coin != Coin.nano &&
coin != Coin.stellar &&
coin != Coin.stellarTestnet &&
coin != Coin.tezos)
if (ref.watch(pWallets.select((value) => value.getWallet(walletId)))
is MultiAddressInterface ||
supportsSpark)
SecondaryButton(
buttonHeight: ButtonHeight.l,
onPressed: generateNewAddress,
onPressed: supportsSpark && _showSparkAddress
? generateNewSparkAddress
: generateNewAddress,
label: "Generate new address",
),
const SizedBox(
@ -226,7 +477,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
child: QrImageView(
data: AddressUtils.buildUriString(
coin,
receivingAddress,
_qrcodeContent ?? "",
{},
),
size: 200,
@ -267,7 +518,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
RouteGenerator.generateRoute(
RouteSettings(
name: GenerateUriQrCodeView.routeName,
arguments: Tuple2(coin, receivingAddress),
arguments: Tuple2(coin, _qrcodeContent ?? ""),
),
),
],
@ -284,7 +535,7 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
builder: (_) => GenerateUriQrCodeView(
coin: coin,
receivingAddress: receivingAddress,
receivingAddress: _qrcodeContent ?? "",
),
settings: const RouteSettings(
name: GenerateUriQrCodeView.routeName,

View file

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

View file

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

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_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/themes/stack_colors.dart';
@ -61,6 +62,7 @@ class _WDesktopWalletSummaryState extends ConsumerState<DesktopWalletSummary> {
),
);
final coin = ref.watch(pWalletCoin(widget.walletId));
final isFiro = coin == Coin.firo || coin == Coin.firoTestNet;
final locale = ref.watch(
localeServiceChangeNotifierProvider.select((value) => value.locale));
@ -82,29 +84,30 @@ class _WDesktopWalletSummaryState extends ConsumerState<DesktopWalletSummary> {
ref.watch(walletBalanceToggleStateProvider.state).state ==
WalletBalanceToggleState.available;
Balance balance = widget.isToken
? ref.watch(tokenServiceProvider.select((value) => value!.balance))
: ref.watch(pWalletBalance(walletId));
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;
Amount balanceToShow;
if (coin == Coin.firo || coin == Coin.firoTestNet) {
final balanceSecondary = ref.watch(pWalletBalanceSecondary(walletId));
final showPrivate =
ref.watch(walletPrivateBalanceToggleStateProvider.state).state ==
WalletBalanceToggleState.available;
case FiroType.lelantus:
final balance = ref.watch(pWalletBalanceSecondary(walletId));
balanceToShow = _showAvailable ? balance.spendable : balance.total;
break;
if (_showAvailable) {
balanceToShow =
showPrivate ? balanceSecondary.spendable : balance.spendable;
} else {
balanceToShow = showPrivate ? balanceSecondary.total : balance.total;
case FiroType.public:
final balance = ref.watch(pWalletBalance(walletId));
balanceToShow = _showAvailable ? balance.spendable : balance.total;
break;
}
} else {
if (_showAvailable) {
balanceToShow = balance.spendable;
} else {
balanceToShow = balance.total;
}
Balance balance = widget.isToken
? ref.watch(tokenServiceProvider.select((value) => value!.balance))
: ref.watch(pWalletBalance(walletId));
balanceToShow = _showAvailable ? balance.spendable : balance.total;
}
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/lelantus_coins/lelantus_coins_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart';
import 'package:stackwallet/pages_desktop_specific/spark_coins/spark_coins_view.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
@ -32,7 +33,8 @@ enum _WalletOptions {
deleteWallet,
changeRepresentative,
showXpub,
lelantusCoins;
lelantusCoins,
sparkCoins;
String get prettyName {
switch (this) {
@ -46,6 +48,8 @@ enum _WalletOptions {
return "Show xPub";
case _WalletOptions.lelantusCoins:
return "Lelantus Coins";
case _WalletOptions.sparkCoins:
return "Spark Coins";
}
}
}
@ -89,6 +93,9 @@ class WalletOptionsButton extends StatelessWidget {
onFiroShowLelantusCoins: () async {
Navigator.of(context).pop(_WalletOptions.lelantusCoins);
},
onFiroShowSparkCoins: () async {
Navigator.of(context).pop(_WalletOptions.sparkCoins);
},
walletId: walletId,
);
},
@ -191,6 +198,15 @@ class WalletOptionsButton extends StatelessWidget {
),
);
break;
case _WalletOptions.sparkCoins:
unawaited(
Navigator.of(context).pushNamed(
SparkCoinsView.routeName,
arguments: walletId,
),
);
break;
}
}
},
@ -224,6 +240,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget {
required this.onShowXpubPressed,
required this.onChangeRepPressed,
required this.onFiroShowLelantusCoins,
required this.onFiroShowSparkCoins,
required this.walletId,
}) : super(key: key);
@ -232,6 +249,7 @@ class WalletOptionsPopupMenu extends ConsumerWidget {
final VoidCallback onShowXpubPressed;
final VoidCallback onChangeRepPressed;
final VoidCallback onFiroShowLelantusCoins;
final VoidCallback onFiroShowSparkCoins;
final String walletId;
@override
@ -374,6 +392,43 @@ class WalletOptionsPopupMenu extends ConsumerWidget {
),
),
),
if (firoDebug)
const SizedBox(
height: 8,
),
if (firoDebug)
TransparentButton(
onPressed: onFiroShowSparkCoins,
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SvgPicture.asset(
Assets.svg.eye,
width: 20,
height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconLeft,
),
const SizedBox(width: 14),
Expanded(
child: Text(
_WalletOptions.sparkCoins.prettyName,
style: STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
),
],
),
),
),
if (xpubEnabled)
const SizedBox(
height: 8,

View file

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

View file

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

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:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
final previewTxButtonStateProvider = StateProvider.autoDispose<bool>((_) {
return false;
final pSendAmount = StateProvider.autoDispose<Amount?>((_) => null);
final pValidSendToAddress = StateProvider.autoDispose<bool>((_) => false);
final pValidSparkSendToAddress = StateProvider.autoDispose<bool>((_) => false);
final pPreviewTxButtonEnabled =
Provider.autoDispose.family<bool, Coin>((ref, coin) {
final amount = ref.watch(pSendAmount) ?? Amount.zero;
// TODO [prio=low]: move away from Coin
if (coin == Coin.firo || coin == Coin.firoTestNet) {
if (ref.watch(publicPrivateBalanceStateProvider) == FiroType.lelantus) {
return ref.watch(pValidSendToAddress) &&
!ref.watch(pValidSparkSendToAddress) &&
amount > Amount.zero;
} else {
return (ref.watch(pValidSendToAddress) ||
ref.watch(pValidSparkSendToAddress)) &&
amount > Amount.zero;
}
} else {
return ref.watch(pValidSendToAddress) && amount > Amount.zero;
}
});
final previewTokenTxButtonStateProvider = StateProvider.autoDispose<bool>((_) {

View file

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

View file

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

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/syncing_preferences_settings.dart';
import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/tor_settings/tor_settings.dart';
import 'package:stackwallet/pages_desktop_specific/spark_coins/spark_coins_view.dart';
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
@ -1858,6 +1859,20 @@ class RouteGenerator {
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case SparkCoinsView.routeName:
if (args is String) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => SparkCoinsView(
walletId: args,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case DesktopCoinControlView.routeName:
if (args is String) {
return getRoute(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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/wallets/crypto_currency/crypto_currency.dart';
import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_hd_currency.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
class Firo extends Bip39HDCurrency {
Firo(super.network) {
@ -132,9 +133,15 @@ class Firo extends Bip39HDCurrency {
coinlib.Address.fromString(address, networkParams);
return true;
} catch (_) {
return false;
return validateSparkAddress(address);
}
// TODO: implement validateAddress for spark addresses?
}
bool validateSparkAddress(String address) {
return SparkInterface.validateSparkAddress(
address: address,
isTestNet: network == CryptoCurrencyNetwork.test,
);
}
@override

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

@ -625,9 +625,19 @@ mixin ElectrumXInterface on Bip39HDWallet {
// TODO: use coinlib
final txb = bitcoindart.TransactionBuilder(
network: bitcoindart.testnet,
network: bitcoindart.NetworkType(
messagePrefix: cryptoCurrency.networkParams.messagePrefix,
bech32: cryptoCurrency.networkParams.bech32Hrp,
bip32: bitcoindart.Bip32Type(
public: cryptoCurrency.networkParams.pubHDPrefix,
private: cryptoCurrency.networkParams.privHDPrefix,
),
pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix,
scriptHash: cryptoCurrency.networkParams.p2shPrefix,
wif: cryptoCurrency.networkParams.wifPrefix,
),
);
txb.setVersion(1);
txb.setVersion(1); // TODO possibly override this for certain coins?
// Add transaction inputs
for (var i = 0; i < utxoSigningData.length; i++) {
@ -1641,7 +1651,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
@override
Future<void> updateUTXOs() async {
final allAddresses = await fetchAllOwnAddresses();
final allAddresses = await fetchAddressesForElectrumXScan();
try {
final fetchedUtxoList = <List<Map<String, dynamic>>>[];
@ -1856,7 +1866,7 @@ mixin ElectrumXInterface on Bip39HDWallet {
int estimateTxFee({required int vSize, required int feeRatePerKB});
Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB);
Future<List<Address>> fetchAllOwnAddresses();
Future<List<Address>> fetchAddressesForElectrumXScan();
/// Certain coins need to check if the utxo should be marked
/// as blocked as well as give a reason.

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -659,6 +659,15 @@ packages:
relative: true
source: path
version: "0.0.1"
flutter_libsparkmobile:
dependency: "direct main"
description:
path: "."
ref: "6e2b2650a84fc5832dc676f8d016e530ae77851f"
resolved-ref: "6e2b2650a84fc5832dc676f8d016e530ae77851f"
url: "https://github.com/cypherstack/flutter_libsparkmobile.git"
source: git
version: "0.0.1"
flutter_lints:
dependency: "direct dev"
description:

View file

@ -27,6 +27,11 @@ dependencies:
lelantus:
path: ./crypto_plugins/flutter_liblelantus
flutter_libsparkmobile:
git:
url: https://github.com/cypherstack/flutter_libsparkmobile.git
ref: d54b4a1f492e48696c3df27eb8c31131a681cbc2
flutter_libmonero:
path: ./crypto_plugins/flutter_libmonero
@ -160,7 +165,11 @@ dependencies:
url: https://github.com/cypherstack/tezart.git
ref: 8a7070f533e63dd150edae99476f6853bfb25913
socks5_proxy: ^1.0.3+dev.3
coinlib_flutter: ^1.0.0
coinlib_flutter:
git:
url: https://github.com/cypherstack/coinlib.git
path: coinlib_flutter
ref: 4f549b8b511a63fdc1f44796ab43b10f586635cd
convert: ^3.1.1
flutter_hooks: ^0.20.3
meta: ^1.9.1

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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