Merge pull request #898 from cypherstack/firo_exchange_addresses

Firo exchange addresses and bug fixes
This commit is contained in:
julian-CStack 2024-06-27 17:04:49 -06:00 committed by GitHub
commit a4d30e17b7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 809 additions and 455 deletions

@ -1 +1 @@
Subproject commit f1cfc0cbb675c5e2d03c30152514fcbb28a465f4
Subproject commit dd2d493199dd9c697abc8bc6bc94f466005bf70a

View file

@ -12,6 +12,7 @@ import '../../electrumx_rpc/electrumx_client.dart';
import '../../utilities/extensions/extensions.dart';
import '../../utilities/logger.dart';
import '../../utilities/stack_file_system.dart';
import '../../wallets/crypto_currency/crypto_currency.dart';
part 'firo_cache_coordinator.dart';
part 'firo_cache_reader.dart';
@ -31,29 +32,39 @@ void _debugLog(Object? object) {
abstract class _FiroCache {
static const int _setCacheVersion = 1;
static const int _tagsCacheVersion = 2;
static const String sparkSetCacheFileName =
"spark_set_v$_setCacheVersion.sqlite3";
static const String sparkUsedTagsCacheFileName =
"spark_tags_v$_tagsCacheVersion.sqlite3";
static Database? _setCacheDB;
static Database? _usedTagsCacheDB;
static Database get setCacheDB {
if (_setCacheDB == null) {
static final networks = [
CryptoCurrencyNetwork.main,
CryptoCurrencyNetwork.test,
];
static String sparkSetCacheFileName(CryptoCurrencyNetwork network) =>
network == CryptoCurrencyNetwork.main
? "spark_set_v$_setCacheVersion.sqlite3"
: "spark_set_v${_setCacheVersion}_${network.name}.sqlite3";
static String sparkUsedTagsCacheFileName(CryptoCurrencyNetwork network) =>
network == CryptoCurrencyNetwork.main
? "spark_tags_v$_tagsCacheVersion.sqlite3"
: "spark_tags_v${_tagsCacheVersion}_${network.name}.sqlite3";
static final Map<CryptoCurrencyNetwork, Database> _setCacheDB = {};
static final Map<CryptoCurrencyNetwork, Database> _usedTagsCacheDB = {};
static Database setCacheDB(CryptoCurrencyNetwork network) {
if (_setCacheDB[network] == null) {
throw Exception(
"FiroCache.init() must be called before accessing FiroCache.db!",
);
}
return _setCacheDB!;
return _setCacheDB[network]!;
}
static Database get usedTagsCacheDB {
if (_usedTagsCacheDB == null) {
static Database usedTagsCacheDB(CryptoCurrencyNetwork network) {
if (_usedTagsCacheDB[network] == null) {
throw Exception(
"FiroCache.init() must be called before accessing FiroCache.db!",
);
}
return _usedTagsCacheDB!;
return _usedTagsCacheDB[network]!;
}
static Future<void>? _initFuture;
@ -63,30 +74,34 @@ abstract class _FiroCache {
final sqliteDir =
await StackFileSystem.applicationFiroCacheSQLiteDirectory();
final sparkSetCacheFile = File("${sqliteDir.path}/$sparkSetCacheFileName");
final sparkUsedTagsCacheFile =
File("${sqliteDir.path}/$sparkUsedTagsCacheFileName");
for (final network in networks) {
final sparkSetCacheFile =
File("${sqliteDir.path}/${sparkSetCacheFileName(network)}");
if (!(await sparkSetCacheFile.exists())) {
await _createSparkSetCacheDb(sparkSetCacheFile.path);
}
if (!(await sparkUsedTagsCacheFile.exists())) {
await _createSparkUsedTagsCacheDb(sparkUsedTagsCacheFile.path);
}
final sparkUsedTagsCacheFile =
File("${sqliteDir.path}/${sparkUsedTagsCacheFileName(network)}");
_setCacheDB = sqlite3.open(
sparkSetCacheFile.path,
mode: OpenMode.readWrite,
);
_usedTagsCacheDB = sqlite3.open(
sparkUsedTagsCacheFile.path,
mode: OpenMode.readWrite,
);
if (!(await sparkSetCacheFile.exists())) {
await _createSparkSetCacheDb(sparkSetCacheFile.path);
}
if (!(await sparkUsedTagsCacheFile.exists())) {
await _createSparkUsedTagsCacheDb(sparkUsedTagsCacheFile.path);
}
_setCacheDB[network] = sqlite3.open(
sparkSetCacheFile.path,
mode: OpenMode.readWrite,
);
_usedTagsCacheDB[network] = sqlite3.open(
sparkUsedTagsCacheFile.path,
mode: OpenMode.readWrite,
);
}
}
static Future<void> _deleteAllCache() async {
static Future<void> _deleteAllCache(CryptoCurrencyNetwork network) async {
final start = DateTime.now();
setCacheDB.execute(
setCacheDB(network).execute(
"""
DELETE FROM SparkSet;
DELETE FROM SparkCoin;
@ -94,7 +109,7 @@ abstract class _FiroCache {
VACUUM;
""",
);
usedTagsCacheDB.execute(
usedTagsCacheDB(network).execute(
"""
DELETE FROM SparkUsedCoinTags;
VACUUM;

View file

@ -5,7 +5,7 @@ typedef LTagPair = ({String tag, String txid});
/// Wrapper class for [_FiroCache] as [_FiroCache] should eventually be handled in a
/// background isolate and [FiroCacheCoordinator] should manage that isolate
abstract class FiroCacheCoordinator {
static _FiroCacheWorker? _worker;
static final Map<CryptoCurrencyNetwork, _FiroCacheWorker> _workers = {};
static bool _init = false;
static Future<void> init() async {
@ -14,20 +14,22 @@ abstract class FiroCacheCoordinator {
}
_init = true;
await _FiroCache.init();
_worker = await _FiroCacheWorker.spawn();
for (final network in _FiroCache.networks) {
_workers[network] = await _FiroCacheWorker.spawn(network);
}
}
static Future<void> clearSharedCache() async {
return await _FiroCache._deleteAllCache();
static Future<void> clearSharedCache(CryptoCurrencyNetwork network) async {
return await _FiroCache._deleteAllCache(network);
}
static Future<String> getSparkCacheSize() async {
static Future<String> getSparkCacheSize(CryptoCurrencyNetwork network) async {
final dir = await StackFileSystem.applicationFiroCacheSQLiteDirectory();
final setCacheFile = File(
"${dir.path}/${_FiroCache.sparkSetCacheFileName}",
"${dir.path}/${_FiroCache.sparkSetCacheFileName(network)}",
);
final usedTagsCacheFile = File(
"${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName}",
"${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName(network)}",
);
final int bytes =
((await setCacheFile.exists()) ? await setCacheFile.length() : 0) +
@ -51,13 +53,14 @@ abstract class FiroCacheCoordinator {
static Future<void> runFetchAndUpdateSparkUsedCoinTags(
ElectrumXClient client,
CryptoCurrencyNetwork network,
) async {
final count = await FiroCacheCoordinator.getUsedCoinTagsCount();
final count = await FiroCacheCoordinator.getUsedCoinTagsCount(network);
final unhashedTags = await client.getSparkUnhashedUsedCoinsTagsWithTxHashes(
startNumber: count,
);
if (unhashedTags.isNotEmpty) {
await _worker!.runTask(
await _workers[network]!.runTask(
FCTask(
func: FCFuncName._updateSparkUsedTagsWith,
data: unhashedTags,
@ -69,10 +72,12 @@ abstract class FiroCacheCoordinator {
static Future<void> runFetchAndUpdateSparkAnonSetCacheForGroupId(
int groupId,
ElectrumXClient client,
CryptoCurrencyNetwork network,
) async {
final blockhashResult =
await FiroCacheCoordinator.getLatestSetInfoForGroupId(
groupId,
network,
);
final blockHash = blockhashResult?.blockHash ?? "";
@ -81,7 +86,7 @@ abstract class FiroCacheCoordinator {
startBlockHash: blockHash.toHexReversedFromBase64,
);
await _worker!.runTask(
await _workers[network]!.runTask(
FCTask(
func: FCFuncName._updateSparkAnonSetCoinsWith,
data: (groupId, json),
@ -91,17 +96,22 @@ abstract class FiroCacheCoordinator {
// ===========================================================================
static Future<Set<String>> getUsedCoinTags(int startNumber) async {
static Future<Set<String>> getUsedCoinTags(
int startNumber,
CryptoCurrencyNetwork network,
) async {
final result = await _Reader._getSparkUsedCoinTags(
startNumber,
db: _FiroCache.usedTagsCacheDB,
db: _FiroCache.usedTagsCacheDB(network),
);
return result.map((e) => e["tag"] as String).toSet();
}
static Future<int> getUsedCoinTagsCount() async {
static Future<int> getUsedCoinTagsCount(
CryptoCurrencyNetwork network,
) async {
final result = await _Reader._getUsedCoinTagsCount(
db: _FiroCache.usedTagsCacheDB,
db: _FiroCache.usedTagsCacheDB(network),
);
if (result.isEmpty) {
return 0;
@ -111,13 +121,14 @@ abstract class FiroCacheCoordinator {
static Future<List<LTagPair>> getUsedCoinTxidsFor({
required List<String> tags,
required CryptoCurrencyNetwork network,
}) async {
if (tags.isEmpty) {
return [];
}
final result = await _Reader._getUsedCoinTxidsFor(
tags,
db: _FiroCache.usedTagsCacheDB,
db: _FiroCache.usedTagsCacheDB(network),
);
if (result.isEmpty) {
@ -135,20 +146,22 @@ abstract class FiroCacheCoordinator {
static Future<Set<String>> getUsedCoinTagsFor({
required String txid,
required CryptoCurrencyNetwork network,
}) async {
final result = await _Reader._getUsedCoinTagsFor(
txid,
db: _FiroCache.usedTagsCacheDB,
db: _FiroCache.usedTagsCacheDB(network),
);
return result.map((e) => e["tag"] as String).toSet();
}
static Future<bool> checkTagIsUsed(
String tag,
CryptoCurrencyNetwork network,
) async {
return await _Reader._checkTagIsUsed(
tag,
db: _FiroCache.usedTagsCacheDB,
db: _FiroCache.usedTagsCacheDB(network),
);
}
@ -161,10 +174,11 @@ abstract class FiroCacheCoordinator {
})>> getSetCoinsForGroupId(
int groupId, {
int? newerThanTimeStamp,
required CryptoCurrencyNetwork network,
}) async {
final resultSet = await _Reader._getSetCoinsForGroupId(
groupId,
db: _FiroCache.setCacheDB,
db: _FiroCache.setCacheDB(network),
newerThanTimeStamp: newerThanTimeStamp,
);
return resultSet
@ -187,10 +201,11 @@ abstract class FiroCacheCoordinator {
int timestampUTC,
})?> getLatestSetInfoForGroupId(
int groupId,
CryptoCurrencyNetwork network,
) async {
final result = await _Reader._getLatestSetInfoForGroupId(
groupId,
db: _FiroCache.setCacheDB,
db: _FiroCache.setCacheDB(network),
);
if (result.isEmpty) {
@ -206,10 +221,11 @@ abstract class FiroCacheCoordinator {
static Future<bool> checkSetInfoForGroupIdExists(
int groupId,
CryptoCurrencyNetwork network,
) async {
return await _Reader._checkSetInfoForGroupIdExists(
groupId,
db: _FiroCache.setCacheDB,
db: _FiroCache.setCacheDB(network),
);
}
}

View file

@ -25,11 +25,12 @@ class _FiroCacheWorker {
return await completer.future;
}
static Future<_FiroCacheWorker> spawn() async {
static Future<_FiroCacheWorker> spawn(CryptoCurrencyNetwork network) async {
final dir = await StackFileSystem.applicationFiroCacheSQLiteDirectory();
final setCacheFilePath = "${dir.path}/${_FiroCache.sparkSetCacheFileName}";
final setCacheFilePath =
"${dir.path}/${_FiroCache.sparkSetCacheFileName(network)}";
final usedTagsCacheFilePath =
"${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName}";
"${dir.path}/${_FiroCache.sparkUsedTagsCacheFileName(network)}";
final initPort = RawReceivePort();
final connection = Completer<(ReceivePort, SendPort)>.sync();

View file

@ -1043,7 +1043,7 @@ class ElectrumXClient {
final start = DateTime.now();
final response = await request(
requestID: requestID,
command: "spark.getmempooltxids",
command: "spark.getmempoolsparktxids",
);
final txids = List<String>.from(response as List)
@ -1072,7 +1072,7 @@ class ElectrumXClient {
final start = DateTime.now();
final response = await request(
requestID: requestID,
command: "spark.getmempooltxs",
command: "spark.getmempoolsparktxs",
args: [
{
"txids": txids,
@ -1087,10 +1087,10 @@ class ElectrumXClient {
(
txid: entry.key,
serialContext:
List<String>.from(entry.value["Serial_context"] as List),
List<String>.from(entry.value["serial_context"] as List),
// the space after lTags is required lol
lTags: List<String>.from(entry.value["lTags "] as List),
coins: List<String>.from(entry.value["Coins"] as List),
coins: List<String>.from(entry.value["coins"] as List),
),
);
}
@ -1142,6 +1142,38 @@ class ElectrumXClient {
}
// ===========================================================================
Future<bool> isMasterNodeCollateral({
String? requestID,
required String txid,
required int index,
}) async {
try {
final start = DateTime.now();
final response = await request(
requestID: requestID,
command: "blockchain.checkifmncollateral",
args: [
txid,
index.toString(),
],
);
Logging.instance.log(
"Finished ElectrumXClient.isMasterNodeCollateral, "
"response: $response, "
"Duration=${DateTime.now().difference(start)}",
level: LogLevel.Info,
);
return response as bool;
} catch (e) {
Logging.instance.log(e, level: LogLevel.Error);
rethrow;
}
}
// ===========================================================================
/// Get the current fee rate.
///
/// Returns a map with the kay "rate" that corresponds to the free rate in satoshis

View file

@ -0,0 +1,87 @@
import 'dart:typed_data';
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
const OP_EXCHANGEADDR = 0xe0;
class EXP2PKHAddress implements coinlib.Address {
/// The 160bit public key or redeemScript hash for the base58 address
final Uint8List _hash;
/// The network and address type version of the address
final Uint8List version;
String? _encodedCache;
EXP2PKHAddress._(Uint8List hash, this.version) : _hash = hash {
if (version.length != 3) {
throw ArgumentError(
"version bytes length must be 3",
);
}
}
factory EXP2PKHAddress.fromString(String encoded, Uint8List versionBytes) {
if (versionBytes.length != 3) {
throw ArgumentError(
"version bytes length must be 3",
);
}
final data = coinlib.base58Decode(encoded);
if (data.length != 23) throw coinlib.InvalidAddress();
final version = data.sublist(0, 3);
for (int i = 0; i < 3; i++) {
if (version[i] != versionBytes[i]) {
throw Exception("EX address version bytes do not match");
}
}
final payload = data.sublist(3);
final addr = EXP2PKHAddress._(payload, version);
addr._encodedCache = encoded;
return addr;
}
@override
String toString() => _encodedCache.toString();
@override
coinlib.Program get program => EXP2PKH.fromHash(_hash);
}
class EXP2PKH implements coinlib.Program {
static const template =
"OP_EXCHANGEADDR OP_DUP OP_HASH160 <20-bytes> OP_EQUALVERIFY OP_CHECKSIG";
@override
final coinlib.Script script;
EXP2PKH.fromScript(this.script);
factory EXP2PKH.fromHash(Uint8List pkHash) {
final List<coinlib.ScriptOp> ops = [
coinlib.ScriptOpCode(OP_EXCHANGEADDR),
];
final parts = template.split(" ").sublist(1);
for (final name in parts) {
if (name.startsWith("OP_")) {
ops.add(
coinlib.ScriptOpCode(
coinlib.scriptOpNameToCode[name.substring(3)]!,
),
);
} else if (name == "<20-bytes>") {
ops.add(coinlib.ScriptPushData(pkHash));
} else {
throw Exception("Something went wrong in this hacked code");
}
}
return EXP2PKH.fromScript(coinlib.Script(ops));
}
}

View file

@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../../../frost_route_generator.dart';
import '../../../../wallet_view/transaction_views/transaction_details_view.dart';
import '../../../../../providers/frost_wallet/frost_wallet_providers.dart';
import '../../../../../services/frost.dart';
import '../../../../../themes/stack_colors.dart';
@ -17,6 +16,8 @@ import '../../../../../widgets/desktop/secondary_button.dart';
import '../../../../../widgets/detail_item.dart';
import '../../../../../widgets/dialogs/simple_mobile_dialog.dart';
import '../../../../../widgets/frost_step_user_steps.dart';
import '../../../../../widgets/qr.dart';
import '../../../../wallet_view/transaction_views/transaction_details_view.dart';
class FrostCreateStep1a extends ConsumerStatefulWidget {
const FrostCreateStep1a({super.key});
@ -162,14 +163,9 @@ class _FrostCreateStep1aState extends ConsumerState<FrostCreateStep1a> {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
QrImageView(
QR(
data: ref.watch(pFrostMultisigConfig.state).state ?? "Error",
size: 220,
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
],
),

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../../frost_route_generator.dart';
import '../../../../providers/db/main_db_provider.dart';
@ -23,6 +22,7 @@ import '../../../../widgets/detail_item.dart';
import '../../../../widgets/dialogs/frost/frost_error_dialog.dart';
import '../../../../widgets/dialogs/simple_mobile_dialog.dart';
import '../../../../widgets/frost_step_user_steps.dart';
import '../../../../widgets/qr.dart';
import '../../../wallet_view/transaction_views/transaction_details_view.dart';
class FrostReshareStep1a extends ConsumerStatefulWidget {
@ -239,14 +239,9 @@ class _FrostReshareStep1aState extends ConsumerState<FrostReshareStep1a> {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
QrImageView(
QR(
data: ref.watch(pFrostResharingData).resharerRConfig!,
size: 220,
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
],
),

View file

@ -14,7 +14,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:tuple/tuple.dart';
import '../../../app_config.dart';
@ -37,6 +36,7 @@ import '../../../wallets/wallet/impl/firo_wallet.dart';
import '../../../widgets/background.dart';
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../widgets/desktop/secondary_button.dart';
import '../../../widgets/qr.dart';
import '../../../widgets/rounded_container.dart';
import '../../../widgets/rounded_white_container.dart';
import '../../../widgets/stack_dialog.dart';
@ -751,7 +751,7 @@ class _Step4ViewState extends ConsumerState<Step4View> {
height: 24,
),
Center(
child: QrImageView(
child: QR(
// TODO: grab coin uri scheme from somewhere
// data: "${coin.uriScheme}:$receivingAddress",
data: model.trade!.payInAddress,
@ -759,9 +759,6 @@ class _Step4ViewState extends ConsumerState<Step4View> {
.size
.width /
2,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
const SizedBox(

View file

@ -16,7 +16,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:tuple/tuple.dart';
import 'package:url_launcher/url_launcher.dart';
@ -50,6 +49,7 @@ import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../widgets/custom_buttons/blue_text_button.dart';
import '../../widgets/desktop/desktop_dialog.dart';
import '../../widgets/desktop/secondary_button.dart';
import '../../widgets/qr.dart';
import '../../widgets/rounded_container.dart';
import '../../widgets/rounded_white_container.dart';
import '../../widgets/stack_dialog.dart';
@ -808,15 +808,9 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
child: SizedBox(
width: width + 20,
height: width + 20,
child: QrImageView(
child: QR(
data: trade.payInAddress,
size: width,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),

View file

@ -14,15 +14,11 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:tuple/tuple.dart';
import '../../../exceptions/wallet/insufficient_balance_exception.dart';
import '../../../models/paynym/paynym_account_lite.dart';
import '../../../notifications/show_flush_bar.dart';
import 'confirm_paynym_connect_dialog.dart';
import '../paynym_home_view.dart';
import '../subwidgets/paynym_bot.dart';
import '../../send_view/confirm_transaction_view.dart';
import '../../send_view/send_view.dart';
import '../../../providers/global/locale_provider.dart';
import '../../../providers/global/wallets_provider.dart';
import '../../../route_generator.dart';
@ -37,9 +33,14 @@ import '../../../widgets/desktop/desktop_dialog.dart';
import '../../../widgets/desktop/primary_button.dart';
import '../../../widgets/desktop/secondary_button.dart';
import '../../../widgets/loading_indicator.dart';
import '../../../widgets/qr.dart';
import '../../../widgets/rounded_container.dart';
import '../../../widgets/stack_dialog.dart';
import 'package:tuple/tuple.dart';
import '../../send_view/confirm_transaction_view.dart';
import '../../send_view/send_view.dart';
import '../paynym_home_view.dart';
import '../subwidgets/paynym_bot.dart';
import 'confirm_paynym_connect_dialog.dart';
class PaynymDetailsPopup extends ConsumerStatefulWidget {
const PaynymDetailsPopup({
@ -365,12 +366,10 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
const SizedBox(
width: 20,
),
QrImageView(
QR(
padding: const EdgeInsets.all(0),
size: 100,
data: widget.accountLite.code,
foregroundColor:
Theme.of(context).extension<StackColors>()!.textDark,
),
],
),

View file

@ -12,10 +12,9 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../models/paynym/paynym_account.dart';
import '../../../notifications/show_flush_bar.dart';
import '../subwidgets/paynym_bot.dart';
import '../../../themes/stack_colors.dart';
import '../../../utilities/assets.dart';
import '../../../utilities/text_styles.dart';
@ -23,6 +22,8 @@ import '../../../utilities/util.dart';
import '../../../widgets/custom_buttons/blue_text_button.dart';
import '../../../widgets/desktop/desktop_dialog.dart';
import '../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../widgets/qr.dart';
import '../subwidgets/paynym_bot.dart';
class PaynymQrPopup extends StatelessWidget {
const PaynymQrPopup({
@ -157,12 +158,10 @@ class PaynymQrPopup extends StatelessWidget {
const SizedBox(
width: 20,
),
QrImageView(
QR(
padding: const EdgeInsets.all(0),
size: 130,
data: paynymAccount.nonSegwitPaymentCode.code,
foregroundColor:
Theme.of(context).extension<StackColors>()!.textDark,
),
],
),

View file

@ -14,19 +14,15 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../exceptions/wallet/insufficient_balance_exception.dart';
import '../../../models/paynym/paynym_account_lite.dart';
import '../../../notifications/show_flush_bar.dart';
import '../dialogs/confirm_paynym_connect_dialog.dart';
import 'paynym_bot.dart';
import '../../send_view/confirm_transaction_view.dart';
import '../../../pages_desktop_specific/my_stack_view/paynym/desktop_paynym_send_dialog.dart';
import '../../../providers/global/locale_provider.dart';
import '../../../providers/global/wallets_provider.dart';
import '../../../themes/stack_colors.dart';
import '../../../utilities/assets.dart';
import '../../../utilities/text_styles.dart';
import '../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../wallets/models/tx_data.dart';
@ -36,8 +32,12 @@ import '../../../widgets/custom_buttons/paynym_follow_toggle_button.dart';
import '../../../widgets/desktop/desktop_dialog.dart';
import '../../../widgets/desktop/primary_button.dart';
import '../../../widgets/loading_indicator.dart';
import '../../../widgets/qr.dart';
import '../../../widgets/rounded_container.dart';
import '../../../widgets/rounded_white_container.dart';
import '../../send_view/confirm_transaction_view.dart';
import '../dialogs/confirm_paynym_connect_dialog.dart';
import 'paynym_bot.dart';
class DesktopPaynymDetails extends ConsumerStatefulWidget {
const DesktopPaynymDetails({
@ -359,12 +359,10 @@ class _PaynymDetailsPopupState extends ConsumerState<DesktopPaynymDetails> {
const SizedBox(
width: 20,
),
QrImageView(
QR(
padding: const EdgeInsets.all(0),
size: 100,
data: widget.accountLite.code,
foregroundColor:
Theme.of(context).extension<StackColors>()!.textDark,
),
],
),

View file

@ -20,7 +20,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart';
import '../../../db/isar/main_db.dart';
@ -39,6 +38,7 @@ import '../../../widgets/custom_buttons/blue_text_button.dart';
import '../../../widgets/custom_buttons/simple_edit_button.dart';
import '../../../widgets/desktop/primary_button.dart';
import '../../../widgets/desktop/secondary_button.dart';
import '../../../widgets/qr.dart';
import '../../../widgets/rounded_white_container.dart';
import '../../../widgets/stack_dialog.dart';
@ -302,19 +302,13 @@ class _AddressCardState extends ConsumerState<AddressCard> {
Center(
child: RepaintBoundary(
key: _qrKey,
child: QrImageView(
child: QR(
data: AddressUtils.buildUriString(
widget.coin,
address.value,
{},
),
size: 220,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),

View file

@ -11,7 +11,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../db/isar/main_db.dart';
import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
@ -31,6 +30,7 @@ import '../../../widgets/custom_buttons/simple_copy_button.dart';
import '../../../widgets/custom_buttons/simple_edit_button.dart';
import '../../../widgets/desktop/desktop_dialog.dart';
import '../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../widgets/qr.dart';
import '../../../widgets/rounded_white_container.dart';
import '../../../widgets/transaction_card.dart';
import '../../wallet_view/sub_widgets/no_transactions_found.dart';
@ -92,18 +92,13 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
Center(
child: RepaintBoundary(
key: _qrKey,
child: QrImageView(
child: QR(
data: AddressUtils.buildUriString(
ref.watch(pWalletCoin(widget.walletId)),
address.value,
{},
),
size: 220,
backgroundColor:
Theme.of(context).extension<StackColors>()!.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),
@ -289,19 +284,13 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
Center(
child: RepaintBoundary(
key: _qrKey,
child: QrImageView(
child: QR(
data: AddressUtils.buildUriString(
coin,
address.value,
{},
),
size: 220,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),

View file

@ -18,7 +18,6 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart';
import '../../../notifications/show_flush_bar.dart';
@ -31,6 +30,7 @@ import '../../../utilities/util.dart';
import '../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../widgets/desktop/primary_button.dart';
import '../../../widgets/desktop/secondary_button.dart';
import '../../../widgets/qr.dart';
import '../../../widgets/stack_dialog.dart';
class AddressQrPopup extends StatefulWidget {
@ -140,17 +140,13 @@ class _AddressQrPopupState extends State<AddressQrPopup> {
Center(
child: RepaintBoundary(
key: _qrKey,
child: QrImageView(
child: QR(
data: AddressUtils.buildUriString(
widget.coin,
widget.addressString,
{},
),
size: 220,
backgroundColor:
Theme.of(context).extension<StackColors>()!.popupBG,
foregroundColor:
Theme.of(context).extension<StackColors>()!.accentColorDark,
),
),
),

View file

@ -249,17 +249,15 @@ class _WalletAddressesViewState extends ConsumerState<WalletAddressesView> {
walletId: widget.walletId,
addressId: snapshot.data![index],
coin: coin,
onPressed: !isDesktop
? null
: () {
Navigator.of(context).pushNamed(
AddressDetailsView.routeName,
arguments: Tuple2(
snapshot.data![index],
widget.walletId,
),
);
},
onPressed: () {
Navigator.of(context).pushNamed(
AddressDetailsView.routeName,
arguments: Tuple2(
snapshot.data![index],
widget.walletId,
),
);
},
),
);
} else {

View file

@ -20,7 +20,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_svg/svg.dart';
import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart';
import '../../notifications/show_flush_bar.dart';
@ -39,6 +38,7 @@ import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../widgets/desktop/primary_button.dart';
import '../../widgets/desktop/secondary_button.dart';
import '../../widgets/icon_widgets/x_icon.dart';
import '../../widgets/qr.dart';
import '../../widgets/rounded_white_container.dart';
import '../../widgets/stack_dialog.dart';
import '../../widgets/stack_text_field.dart';
@ -215,14 +215,9 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
child: SizedBox(
width: width + 20,
height: width + 20,
child: QrImageView(
child: QR(
data: uriString,
size: width,
backgroundColor:
Theme.of(context).extension<StackColors>()!.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),
@ -556,15 +551,9 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
child: SizedBox(
width: 234,
height: 234,
child: QrImageView(
child: QR(
data: _uriString,
size: 220,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),

View file

@ -16,7 +16,6 @@ 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 '../../models/isar/models/isar_models.dart';
import '../../notifications/show_flush_bar.dart';
@ -44,6 +43,7 @@ import '../../widgets/custom_buttons/blue_text_button.dart';
import '../../widgets/custom_loading_overlay.dart';
import '../../widgets/desktop/primary_button.dart';
import '../../widgets/desktop/secondary_button.dart';
import '../../widgets/qr.dart';
import '../../widgets/rounded_white_container.dart';
import 'addresses/wallet_addresses_view.dart';
import 'generate_receiving_uri_qr_code_view.dart';
@ -575,16 +575,13 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
child: Center(
child: Column(
children: [
QrImageView(
QR(
data: AddressUtils.buildUriString(
coin,
address,
{},
),
size: MediaQuery.of(context).size.width / 2,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
const SizedBox(
height: 20,

View file

@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../../frost_route_generator.dart';
import '../../../wallet_view/transaction_views/transaction_details_view.dart';
import '../../../../providers/frost_wallet/frost_wallet_providers.dart';
import '../../../../providers/global/wallets_provider.dart';
import '../../../../themes/stack_colors.dart';
@ -14,7 +13,9 @@ import '../../../../widgets/custom_buttons/checkbox_text_button.dart';
import '../../../../widgets/custom_buttons/simple_copy_button.dart';
import '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/detail_item.dart';
import '../../../../widgets/qr.dart';
import '../../../../widgets/rounded_white_container.dart';
import '../../../wallet_view/transaction_views/transaction_details_view.dart';
class FrostSendStep1a extends ConsumerStatefulWidget {
const FrostSendStep1a({super.key});
@ -169,14 +170,9 @@ class _FrostSendStep1aState extends ConsumerState<FrostSendStep1a> {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
QrImageView(
QR(
data: ref.watch(pFrostTxData.state).state!.frostMSConfig!,
size: qrImageSize,
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
],
),

View file

@ -56,6 +56,7 @@ import '../../widgets/animated_text.dart';
import '../../widgets/background.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../widgets/custom_buttons/blue_text_button.dart';
import '../../widgets/dialogs/firo_exchange_address_dialog.dart';
import '../../widgets/fee_slider.dart';
import '../../widgets/icon_widgets/addressbook_icon.dart';
import '../../widgets/icon_widgets/clipboard_icon.dart';
@ -127,6 +128,8 @@ class _SendViewState extends ConsumerState<SendView> {
bool _addressToggleFlag = false;
bool _isFiroExWarningDisplayed = false;
bool _cryptoAmountChangeLock = false;
late VoidCallback onCryptoAmountChanged;
@ -394,6 +397,19 @@ class _SendViewState extends ConsumerState<SendView> {
address: address ?? "",
isTestNet: wallet.cryptoCurrency.network.isTestNet,
);
ref.read(pIsExchangeAddress.state).state =
(coin as Firo).isExchangeAddress(address ?? "");
if (ref.read(publicPrivateBalanceStateProvider) == FiroType.spark &&
ref.read(pIsExchangeAddress) &&
!_isFiroExWarningDisplayed) {
_isFiroExWarningDisplayed = true;
showFiroExchangeAddressWarning(
context,
() => _isFiroExWarningDisplayed = false,
);
}
}
ref.read(pValidSendToAddress.notifier).state =
@ -875,7 +891,10 @@ class _SendViewState extends ConsumerState<SendView> {
@override
void initState() {
coin = widget.coin;
ref.refresh(feeSheetSessionCacheProvider);
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.refresh(feeSheetSessionCacheProvider);
ref.refresh(pIsExchangeAddress);
});
_currentFee = 0.toAmountAsRaw(fractionDigits: coin.fractionDigits);
_calculateFeesFuture =
@ -1003,6 +1022,8 @@ class _SendViewState extends ConsumerState<SendView> {
: true);
if (isFiro) {
final isExchangeAddress = ref.watch(pIsExchangeAddress);
ref.listen(publicPrivateBalanceStateProvider, (previous, next) {
selectedUTXOs = {};
@ -1019,6 +1040,19 @@ class _SendViewState extends ConsumerState<SendView> {
);
});
}
if (previous != next &&
next == FiroType.spark &&
isExchangeAddress &&
!_isFiroExWarningDisplayed) {
_isFiroExWarningDisplayed = true;
WidgetsBinding.instance.addPostFrameCallback(
(_) => showFiroExchangeAddressWarning(
context,
() => _isFiroExWarningDisplayed = false,
),
);
}
});
}

View file

@ -14,7 +14,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../../app_config.dart';
import '../../../../notifications/show_flush_bar.dart';
@ -30,6 +29,7 @@ import '../../../../widgets/background.dart';
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../../widgets/custom_buttons/simple_copy_button.dart';
import '../../../../widgets/detail_item.dart';
import '../../../../widgets/qr.dart';
import '../../../../widgets/rounded_white_container.dart';
import '../../../../widgets/stack_dialog.dart';
import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart';
@ -317,15 +317,9 @@ class WalletBackupView extends ConsumerWidget {
child: SizedBox(
width: width + 20,
height: width + 20,
child: QrImageView(
child: QR(
data: data,
size: width,
backgroundColor: Theme.of(context)
.extension<StackColors>()!
.popupBG,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
),

View file

@ -759,6 +759,32 @@ class _WalletNetworkSettingsViewState
),
),
),
SizedBox(
height: isDesktop ? 12 : 9,
),
RoundedWhiteContainer(
borderColor: isDesktop
? Theme.of(context).extension<StackColors>()!.background
: null,
padding:
isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Current height",
textAlign: TextAlign.left,
style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(context)
: STextStyles.smallMed12(context),
),
Text(
ref.watch(pWalletChainHeight(widget.walletId)).toString(),
style: STextStyles.desktopTextExtraExtraSmall(context),
),
],
),
),
SizedBox(
height: isDesktop ? 32 : 20,
),

View file

@ -430,7 +430,9 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
),
if (coin is Firo)
FiroCacheCoordinator
.clearSharedCache(),
.clearSharedCache(
coin.network,
),
],
),
context: context,

View file

@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../db/sqlite/firo_cache.dart';
import '../../../../themes/stack_colors.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../../widgets/background.dart';
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../../widgets/detail_item.dart';
@ -11,10 +12,13 @@ import '../../../../widgets/detail_item.dart';
class SparkInfoView extends ConsumerWidget {
const SparkInfoView({
super.key,
required this.walletId,
});
static const String routeName = "/sparkInfo";
final String walletId;
@override
Widget build(BuildContext context, WidgetRef ref) {
return Background(
@ -37,7 +41,9 @@ class SparkInfoView extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
FutureBuilder(
future: FiroCacheCoordinator.getSparkCacheSize(),
future: FiroCacheCoordinator.getSparkCacheSize(
ref.watch(pWalletCoin(walletId)).network,
),
builder: (_, snapshot) {
String detail = "Loading...";
if (snapshot.connectionState == ConnectionState.done) {

View file

@ -243,6 +243,7 @@ class WalletSettingsWalletSettingsView extends ConsumerWidget {
onPressed: () {
Navigator.of(context).pushNamed(
SparkInfoView.routeName,
arguments: walletId,
);
},
child: Padding(

View file

@ -14,7 +14,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../../notifications/show_flush_bar.dart';
import '../../../../providers/global/wallets_provider.dart';
@ -32,6 +31,7 @@ import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/desktop/secondary_button.dart';
import '../../../../widgets/loading_indicator.dart';
import '../../../../widgets/qr.dart';
import '../../../../widgets/rounded_white_container.dart';
class XPubView extends ConsumerStatefulWidget {
@ -256,11 +256,9 @@ class _XPub extends StatelessWidget {
builder: (child) => RoundedWhiteContainer(
child: child,
),
child: QrImageView(
child: QR(
data: xpub,
size: isDesktop ? 280 : MediaQuery.of(context).size.width / 1.5,
foregroundColor:
Theme.of(context).extension<StackColors>()!.accentColorDark,
),
),
const SizedBox(height: 25),

View file

@ -13,6 +13,7 @@ import '../../../../utilities/text_styles.dart';
import '../../../../utilities/util.dart';
import '../../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../../widgets/breathing.dart';
import '../../../../widgets/desktop/desktop_dialog.dart';
import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../../widgets/trade_card.dart';
@ -49,98 +50,100 @@ class TxListItem extends ConsumerWidget {
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TransactionCardV2(
key: UniqueKey(),
transaction: _tx,
),
TradeCard(
key: Key(
_tx.txid +
_tx.type.name +
_tx.hashCode.toString() +
trade.uuid,
), //
trade: trade,
onTap: () async {
if (Util.isDesktop) {
await showDialog<void>(
context: context,
builder: (context) => Navigator(
initialRoute: TradeDetailsView.routeName,
onGenerateRoute: RouteGenerator.generateRoute,
onGenerateInitialRoutes: (_, __) {
return [
FadePageRoute(
DesktopDialog(
maxHeight: null,
maxWidth: 580,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(
left: 32,
bottom: 16,
child: Breathing(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TransactionCardV2(
key: UniqueKey(),
transaction: _tx,
),
TradeCard(
key: Key(
_tx.txid +
_tx.type.name +
_tx.hashCode.toString() +
trade.uuid,
), //
trade: trade,
onTap: () async {
if (Util.isDesktop) {
await showDialog<void>(
context: context,
builder: (context) => Navigator(
initialRoute: TradeDetailsView.routeName,
onGenerateRoute: RouteGenerator.generateRoute,
onGenerateInitialRoutes: (_, __) {
return [
FadePageRoute(
DesktopDialog(
maxHeight: null,
maxWidth: 580,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(
left: 32,
bottom: 16,
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Trade details",
style: STextStyles.desktopH3(
context),
),
DesktopDialogCloseButton(
onPressedOverride: Navigator.of(
context,
rootNavigator: true,
).pop,
),
],
),
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Trade details",
style:
STextStyles.desktopH3(context),
),
DesktopDialogCloseButton(
onPressedOverride: Navigator.of(
context,
rootNavigator: true,
).pop,
),
],
Flexible(
child: TradeDetailsView(
tradeId: trade.tradeId,
// TODO: [prio:med]
// transactionIfSentFromStack: tx,
transactionIfSentFromStack: null,
walletName: ref
.watch(pWalletName(_tx.walletId)),
walletId: _tx.walletId,
),
),
),
Flexible(
child: TradeDetailsView(
tradeId: trade.tradeId,
// TODO: [prio:med]
// transactionIfSentFromStack: tx,
transactionIfSentFromStack: null,
walletName: ref
.watch(pWalletName(_tx.walletId)),
walletId: _tx.walletId,
),
),
],
],
),
),
const RouteSettings(
name: TradeDetailsView.routeName,
),
),
const RouteSettings(
name: TradeDetailsView.routeName,
),
),
];
},
),
);
} else {
unawaited(
Navigator.of(context).pushNamed(
TradeDetailsView.routeName,
arguments: Tuple4(
trade.tradeId,
_tx,
_tx.walletId,
ref.read(pWalletName(_tx.walletId)),
];
},
),
),
);
}
},
),
],
);
} else {
unawaited(
Navigator.of(context).pushNamed(
TradeDetailsView.routeName,
arguments: Tuple4(
trade.tradeId,
_tx,
_tx.walletId,
ref.read(pWalletName(_tx.walletId)),
),
),
);
}
},
),
],
),
),
);
} else {
@ -149,10 +152,12 @@ class TxListItem extends ConsumerWidget {
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: TransactionCardV2(
// this may mess with combined firo transactions
key: UniqueKey(),
transaction: _tx,
child: Breathing(
child: TransactionCardV2(
// this may mess with combined firo transactions
key: UniqueKey(),
transaction: _tx,
),
),
);
}
@ -165,9 +170,11 @@ class TxListItem extends ConsumerWidget {
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: FusionTxGroupCard(
key: UniqueKey(),
group: group,
child: Breathing(
child: FusionTxGroupCard(
key: UniqueKey(),
group: group,
),
),
);
}

View file

@ -80,7 +80,6 @@ import '../buy_view/buy_in_wallet_view.dart';
import '../cashfusion/cashfusion_view.dart';
import '../coin_control/coin_control_view.dart';
import '../exchange_view/wallet_initiated_exchange_view.dart';
import '../home_view/home_view.dart';
import '../monkey/monkey_view.dart';
import '../notification_views/notifications_view.dart';
import '../ordinals/ordinals_view.dart';
@ -257,40 +256,43 @@ class _WalletViewState extends ConsumerState<WalletView> {
super.dispose();
}
DateTime? _cachedTime;
// DateTime? _cachedTime;
Future<bool> _onWillPop() async {
if (_rescanningOnOpen || _lelantusRescanRecovery) {
return false;
}
final now = DateTime.now();
const timeout = Duration(milliseconds: 1500);
if (_cachedTime == null || now.difference(_cachedTime!) > timeout) {
_cachedTime = now;
unawaited(
showDialog<dynamic>(
context: context,
barrierDismissible: false,
builder: (_) => WillPopScope(
onWillPop: () async {
Navigator.of(context).popUntil(
ModalRoute.withName(HomeView.routeName),
);
_logout();
return false;
},
child: const StackDialog(title: "Tap back again to exit wallet"),
),
).timeout(
timeout,
onTimeout: () => Navigator.of(context).popUntil(
ModalRoute.withName(WalletView.routeName),
),
),
);
}
return false;
_logout();
return true;
// final now = DateTime.now();
// const timeout = Duration(milliseconds: 1500);
// if (_cachedTime == null || now.difference(_cachedTime!) > timeout) {
// _cachedTime = now;
// unawaited(
// showDialog<dynamic>(
// context: context,
// barrierDismissible: false,
// builder: (_) => WillPopScope(
// onWillPop: () async {
// Navigator.of(context).popUntil(
// ModalRoute.withName(HomeView.routeName),
// );
// _logout();
// return false;
// },
// child: const StackDialog(title: "Tap back again to exit wallet"),
// ),
// ).timeout(
// timeout,
// onTimeout: () => Navigator.of(context).popUntil(
// ModalRoute.withName(WalletView.routeName),
// ),
// ),
// );
// }
// return false;
}
void _logout() async {

View file

@ -13,7 +13,6 @@ import 'dart:async';
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../app_config.dart';
import '../../../models/exchange/incomplete_exchange.dart';
@ -37,6 +36,7 @@ import '../../../widgets/desktop/primary_button.dart';
import '../../../widgets/desktop/secondary_button.dart';
import '../../../widgets/desktop/simple_desktop_dialog.dart';
import '../../../widgets/fade_stack.dart';
import '../../../widgets/qr.dart';
import '../subwidgets/desktop_exchange_steps_indicator.dart';
import 'subwidgets/desktop_step_1.dart';
import 'subwidgets/desktop_step_2.dart';
@ -397,7 +397,7 @@ class _StepScaffoldState extends ConsumerState<StepScaffold> {
height: 48,
),
Center(
child: QrImageView(
child: QR(
// TODO: grab coin uri scheme from somewhere
// data: "${coin.uriScheme}:$receivingAddress",
data: ref.watch(
@ -406,9 +406,6 @@ class _StepScaffoldState extends ConsumerState<StepScaffold> {
),
),
size: 290,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
),
const SizedBox(

View file

@ -13,6 +13,7 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import '../../pages/wallets_view/wallets_overview.dart';
import '../../providers/providers.dart';
import '../../themes/coin_icon_provider.dart';
@ -21,6 +22,7 @@ import '../../utilities/amount/amount.dart';
import '../../utilities/text_styles.dart';
import '../../wallets/crypto_currency/crypto_currency.dart';
import '../../wallets/isar/providers/all_wallets_info_provider.dart';
import '../../widgets/breathing.dart';
import '../../widgets/conditional_parent.dart';
import '../../widgets/desktop/desktop_dialog.dart';
import '../../widgets/desktop/desktop_dialog_close_button.dart';
@ -146,71 +148,56 @@ class _DesktopWalletSummaryRowState
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(
() => _hovering = true,
),
onExit: (_) => setState(
() => _hovering = false,
),
child: AnimatedScale(
scale: _hovering ? 1.00 : 0.98,
duration: const Duration(
milliseconds: 200,
),
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(20),
hoverColor: Colors.transparent,
onPressed: _onPressed,
child: Row(
children: [
Expanded(
flex: 4,
child: Row(
children: [
SvgPicture.file(
File(
ref.watch(coinIconProvider(widget.coin)),
),
width: 28,
height: 28,
return Breathing(
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(20),
hoverColor: Colors.transparent,
onPressed: _onPressed,
child: Row(
children: [
Expanded(
flex: 4,
child: Row(
children: [
SvgPicture.file(
File(
ref.watch(coinIconProvider(widget.coin)),
),
const SizedBox(
width: 10,
),
Text(
widget.coin.prettyName,
style:
STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
],
),
),
Expanded(
flex: 4,
child: Text(
widget.walletCount == 1
? "${widget.walletCount} wallet"
: "${widget.walletCount} wallets",
style: STextStyles.desktopTextExtraSmall(context).copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
width: 28,
height: 28,
),
const SizedBox(
width: 10,
),
Text(
widget.coin.prettyName,
style: STextStyles.desktopTextExtraSmall(context).copyWith(
color:
Theme.of(context).extension<StackColors>()!.textDark,
),
),
],
),
),
Expanded(
flex: 4,
child: Text(
widget.walletCount == 1
? "${widget.walletCount} wallet"
: "${widget.walletCount} wallets",
style: STextStyles.desktopTextExtraSmall(context).copyWith(
color:
Theme.of(context).extension<StackColors>()!.textSubtitle1,
),
),
Expanded(
flex: 6,
child: TablePriceInfo(
coin: widget.coin,
),
),
Expanded(
flex: 6,
child: TablePriceInfo(
coin: widget.coin,
),
],
),
),
],
),
),
);

View file

@ -294,7 +294,9 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
width: 2,
),
FutureBuilder(
future: FiroCacheCoordinator.getSparkCacheSize(),
future: FiroCacheCoordinator.getSparkCacheSize(
wallet.cryptoCurrency.network,
),
builder: (_, snapshot) => Text(
snapshot.data ?? "",
),

View file

@ -16,7 +16,6 @@ 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:tuple/tuple.dart';
import '../../../../models/isar/models/isar_models.dart';
@ -42,6 +41,7 @@ import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../../widgets/custom_loading_overlay.dart';
import '../../../../widgets/desktop/desktop_dialog.dart';
import '../../../../widgets/desktop/secondary_button.dart';
import '../../../../widgets/qr.dart';
import '../../../../widgets/rounded_white_container.dart';
class DesktopReceive extends ConsumerStatefulWidget {
@ -476,15 +476,13 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
height: 32,
),
Center(
child: QrImageView(
child: QR(
data: AddressUtils.buildUriString(
coin,
_qrcodeContent ?? "",
{},
),
size: 200,
foregroundColor:
Theme.of(context).extension<StackColors>()!.accentColorDark,
),
),
const SizedBox(

View file

@ -60,6 +60,7 @@ import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../../widgets/desktop/desktop_fee_dialog.dart';
import '../../../../widgets/desktop/primary_button.dart';
import '../../../../widgets/desktop/secondary_button.dart';
import '../../../../widgets/dialogs/firo_exchange_address_dialog.dart';
import '../../../../widgets/fee_slider.dart';
import '../../../../widgets/icon_widgets/addressbook_icon.dart';
import '../../../../widgets/icon_widgets/clipboard_icon.dart';
@ -121,6 +122,8 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
bool _addressToggleFlag = false;
bool _isFiroExWarningDisplayed = false;
bool _cryptoAmountChangeLock = false;
late VoidCallback onCryptoAmountChanged;
@ -706,6 +709,19 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
address: address ?? "",
isTestNet: wallet.cryptoCurrency.network.isTestNet,
);
ref.read(pIsExchangeAddress.state).state =
(coin as Firo).isExchangeAddress(address ?? "");
if (ref.read(publicPrivateBalanceStateProvider) == FiroType.spark &&
ref.read(pIsExchangeAddress) &&
!_isFiroExWarningDisplayed) {
_isFiroExWarningDisplayed = true;
showFiroExchangeAddressWarning(
context,
() => _isFiroExWarningDisplayed = false,
);
}
}
ref.read(pValidSendToAddress.notifier).state =
@ -842,6 +858,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.refresh(feeSheetSessionCacheProvider);
ref.refresh(pIsExchangeAddress);
ref.read(pValidSendToAddress.state).state = false;
ref.read(pValidSparkSendToAddress.state).state = false;
});
@ -944,15 +961,31 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
});
}
final firoType = ref.watch(publicPrivateBalanceStateProvider);
final isExchangeAddress = ref.watch(pIsExchangeAddress);
ref.listen(publicPrivateBalanceStateProvider, (previous, next) {
if (previous != next &&
next == FiroType.spark &&
isExchangeAddress &&
!_isFiroExWarningDisplayed) {
_isFiroExWarningDisplayed = true;
WidgetsBinding.instance.addPostFrameCallback(
(_) => showFiroExchangeAddressWarning(
context,
() => _isFiroExWarningDisplayed = false,
),
);
}
});
final showCoinControl = ref.watch(
prefsChangeNotifierProvider.select(
(value) => value.enableCoinControl,
),
) &&
ref.watch(pWallets).getWallet(walletId) is CoinControlInterface &&
(coin is Firo
? ref.watch(publicPrivateBalanceStateProvider) == FiroType.public
: true);
(coin is Firo ? firoType == FiroType.public : true);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -978,7 +1011,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
DropdownButtonHideUnderline(
child: DropdownButton2(
isExpanded: true,
value: ref.watch(publicPrivateBalanceStateProvider.state).state,
value: firoType,
items: [
DropdownMenuItem(
value: FiroType.spark,
@ -1464,8 +1497,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
if (_address == null || _address!.isEmpty) {
error = null;
} else if (coin is Firo) {
if (ref.watch(publicPrivateBalanceStateProvider) ==
FiroType.lelantus) {
if (firoType == FiroType.lelantus) {
if (_data != null && _data!.contactLabel == _address) {
error = SparkInterface.validateSparkAddress(
address: _data!.address,
@ -1526,15 +1558,13 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
),
if (isStellar ||
(ref.watch(pValidSparkSendToAddress) &&
ref.watch(publicPrivateBalanceStateProvider) !=
FiroType.lelantus))
firoType != FiroType.lelantus))
const SizedBox(
height: 10,
),
if (isStellar ||
(ref.watch(pValidSparkSendToAddress) &&
ref.watch(publicPrivateBalanceStateProvider) !=
FiroType.lelantus))
firoType != FiroType.lelantus))
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,

View file

@ -19,8 +19,7 @@ import '../../../../../providers/global/wallets_provider.dart';
import '../../../../../themes/stack_colors.dart';
import '../../../../../utilities/assets.dart';
import '../../../../../utilities/text_styles.dart';
import '../../../../../wallets/crypto_currency/coins/banano.dart';
import '../../../../../wallets/crypto_currency/coins/firo.dart';
import '../../../../../wallets/crypto_currency/crypto_currency.dart';
import '../../../../../wallets/isar/models/wallet_info.dart';
import '../../../../../wallets/isar/providers/wallet_info_provider.dart';
import '../../../../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
@ -187,7 +186,9 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
onPressed: () async => widget.onFusionPressed?.call(),
),
if (wallet is SparkInterface)
const _MoreFeaturesClearSparkCacheItem(),
_MoreFeaturesClearSparkCacheItem(
cryptoCurrency: wallet.cryptoCurrency,
),
if (wallet is LelantusInterface)
_MoreFeaturesItemBase(
child: Row(
@ -371,10 +372,10 @@ class _MoreFeaturesItemBase extends StatelessWidget {
class _MoreFeaturesClearSparkCacheItem extends StatefulWidget {
const _MoreFeaturesClearSparkCacheItem({
super.key,
required this.cryptoCurrency,
});
static const double iconSizeBG = 46;
static const double iconSize = 24;
final CryptoCurrency cryptoCurrency;
@override
State<_MoreFeaturesClearSparkCacheItem> createState() =>
@ -396,7 +397,9 @@ class _MoreFeaturesClearSparkCacheItemState
}
_onPressedLock = true;
try {
await FiroCacheCoordinator.clearSharedCache();
await FiroCacheCoordinator.clearSharedCache(
widget.cryptoCurrency.network,
);
setState(() {
// trigger rebuild for cache size display
});
@ -434,7 +437,9 @@ class _MoreFeaturesClearSparkCacheItemState
style: STextStyles.w600_20(context),
),
FutureBuilder(
future: FiroCacheCoordinator.getSparkCacheSize(),
future: FiroCacheCoordinator.getSparkCacheSize(
widget.cryptoCurrency.network,
),
builder: (_, snapshot) {
return Text(
snapshot.data ?? "",

View file

@ -9,10 +9,10 @@
*/
import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
import '../../../../themes/stack_colors.dart';
import '../../../../widgets/desktop/desktop_dialog.dart';
import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
import '../../../../widgets/qr.dart';
class QRCodeDesktopPopupContent extends StatelessWidget {
const QRCodeDesktopPopupContent({
@ -39,11 +39,9 @@ class QRCodeDesktopPopupContent extends StatelessWidget {
const SizedBox(
height: 14,
),
QrImageView(
QR(
data: value,
size: 300,
foregroundColor:
Theme.of(context).extension<StackColors>()!.accentColorDark,
),
],
),

View file

@ -9,27 +9,37 @@
*/
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../wallet/public_private_balance_state_provider.dart';
import '../../utilities/amount/amount.dart';
import '../../wallets/crypto_currency/crypto_currency.dart';
import '../wallet/public_private_balance_state_provider.dart';
final pSendAmount = StateProvider.autoDispose<Amount?>((_) => null);
final pValidSendToAddress = StateProvider.autoDispose<bool>((_) => false);
final pValidSparkSendToAddress = StateProvider.autoDispose<bool>((_) => false);
final pIsExchangeAddress = StateProvider<bool>((_) => false);
final pPreviewTxButtonEnabled =
Provider.autoDispose.family<bool, CryptoCurrency>((ref, coin) {
final amount = ref.watch(pSendAmount) ?? Amount.zero;
if (coin is Firo) {
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;
final firoType = ref.watch(publicPrivateBalanceStateProvider);
switch (firoType) {
case FiroType.lelantus:
return ref.watch(pValidSendToAddress) &&
!ref.watch(pValidSparkSendToAddress) &&
amount > Amount.zero;
case FiroType.spark:
return (ref.watch(pValidSendToAddress) ||
ref.watch(pValidSparkSendToAddress)) &&
!ref.watch(pIsExchangeAddress) &&
amount > Amount.zero;
case FiroType.public:
return ref.watch(pValidSendToAddress) && amount > Amount.zero;
}
} else {
return ref.watch(pValidSendToAddress) && amount > Amount.zero;

View file

@ -1982,13 +1982,18 @@ class RouteGenerator {
return _routeError("${settings.name} invalid args: ${args.toString()}");
case SparkInfoView.routeName:
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => const SparkInfoView(),
settings: RouteSettings(
name: settings.name,
),
);
if (args is String) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => SparkInfoView(
walletId: args,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
// == Desktop specific routes ============================================
case CreatePasswordView.routeName:

View file

@ -1,5 +1,8 @@
import 'dart:typed_data';
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
import '../../../models/coinlib/exp2pkh_address.dart';
import '../../../models/isar/models/blockchain_data/address.dart';
import '../../../models/node_model.dart';
import '../../../utilities/amount/amount.dart';
@ -77,6 +80,21 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface {
fractionDigits: fractionDigits,
);
Uint8List get exAddressVersion {
switch (network) {
case CryptoCurrencyNetwork.main:
// https://github.com/firoorg/firo/blob/master/src/chainparams.cpp#L357
return Uint8List.fromList([0x01, 0xb9, 0xbb]);
case CryptoCurrencyNetwork.test:
// https://github.com/firoorg/firo/blob/master/src/chainparams.cpp#L669
return Uint8List.fromList([0x01, 0xb9, 0xb1]);
default:
throw Exception("Unsupported network: $network");
}
}
@override
coinlib.Network get networkParams {
switch (network) {
@ -169,7 +187,11 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface {
coinlib.Address.fromString(address, networkParams);
return true;
} catch (_) {
return validateSparkAddress(address);
if (validateSparkAddress(address)) {
return true;
} else {
return isExchangeAddress(address);
}
}
}
@ -180,6 +202,18 @@ class Firo extends Bip39HDCurrency with ElectrumXCurrencyInterface {
);
}
bool isExchangeAddress(String address) {
try {
EXP2PKHAddress.fromString(
address,
exAddressVersion,
);
return true;
} catch (_) {
return false;
}
}
@override
NodeModel get defaultNode {
switch (network) {

View file

@ -387,6 +387,7 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
parseAnonFees();
final tags = await FiroCacheCoordinator.getUsedCoinTagsFor(
txid: txData["txid"] as String,
network: cryptoCurrency.network,
);
spentSparkCoins = sparkCoinsInvolvedSpent
.where(
@ -631,9 +632,23 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
BigInt.from(jsonUTXO["value"] as int);
if (blocked) {
blockedReason = "Possible masternode output. "
try {
blocked = await electrumXClient.isMasterNodeCollateral(
txid: jsonTX!["txid"] as String,
index: jsonUTXO["tx_pos"] as int,
);
} catch (_) {
// call failed, lock utxo just in case
// it should logically already be blocked
// but just in case
blocked = true;
}
}
if (blocked) {
blockedReason = "Possible masternode collateral. "
"Unlock and spend at your own risk.";
label = "Possible masternode";
label = "Possible masternode collateral";
}
}
@ -698,12 +713,14 @@ class FiroWallet<T extends ElectrumXCurrencyInterface> extends Bip39HDWallet<T>
FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId(
i,
electrumXClient,
cryptoCurrency.network,
),
);
}
final sparkUsedCoinTagsFuture =
FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags(
electrumXClient,
cryptoCurrency.network,
);
// receiving addresses

View file

@ -8,6 +8,7 @@ import 'package:isar/isar.dart';
import '../../../electrumx_rpc/cached_electrumx_client.dart';
import '../../../electrumx_rpc/client_manager.dart';
import '../../../electrumx_rpc/electrumx_client.dart';
import '../../../models/coinlib/exp2pkh_address.dart';
import '../../../models/isar/models/blockchain_data/v2/input_v2.dart';
import '../../../models/isar/models/blockchain_data/v2/output_v2.dart';
import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
@ -24,6 +25,7 @@ import '../../crypto_currency/coins/firo.dart';
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
import '../../models/tx_data.dart';
import '../impl/bitcoin_wallet.dart';
import '../impl/firo_wallet.dart';
import '../impl/peercoin_wallet.dart';
import '../intermediate/bip39_hd_wallet.dart';
import 'cpfp_interface.dart';
@ -725,11 +727,23 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
// Add transaction output
for (var i = 0; i < txData.recipients!.length; i++) {
final address = coinlib.Address.fromString(
normalizeAddress(txData.recipients![i].address),
cryptoCurrency.networkParams,
);
late final coinlib.Address address;
try {
address = coinlib.Address.fromString(
normalizeAddress(txData.recipients![i].address),
cryptoCurrency.networkParams,
);
} catch (_) {
if (this is FiroWallet) {
address = EXP2PKHAddress.fromString(
normalizeAddress(txData.recipients![i].address),
(cryptoCurrency as Firo).exAddressVersion,
);
} else {
rethrow;
}
}
final output = coinlib.Output.fromAddress(
txData.recipients![i].amount.raw,
address,

View file

@ -278,13 +278,17 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final List<Map<String, dynamic>> setMaps = [];
final List<({int groupId, String blockHash})> idAndBlockHashes = [];
for (int i = 1; i <= currentId; i++) {
final resultSet = await FiroCacheCoordinator.getSetCoinsForGroupId(i);
final resultSet = await FiroCacheCoordinator.getSetCoinsForGroupId(
i,
network: cryptoCurrency.network,
);
if (resultSet.isEmpty) {
continue;
}
final info = await FiroCacheCoordinator.getLatestSetInfoForGroupId(
i,
cryptoCurrency.network,
);
if (info == null) {
throw Exception("The `info` should never be null here");
@ -716,13 +720,13 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
return result;
} catch (e) {
Logging.instance.log(
"refreshSparkMempoolData() failed: $e",
"_refreshSparkCoinsMempoolCheck() failed: $e",
level: LogLevel.Error,
);
return [];
} finally {
Logging.instance.log(
"$walletId ${info.name} refreshSparkCoinsMempoolCheck() run "
"$walletId ${info.name} _refreshSparkCoinsMempoolCheck() run "
"duration: ${DateTime.now().difference(start)}",
level: LogLevel.Debug,
);
@ -741,6 +745,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final setExists =
await FiroCacheCoordinator.checkSetInfoForGroupIdExists(
id,
cryptoCurrency.network,
);
if (!setExists) {
groupIds.add(id);
@ -755,6 +760,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
FiroCacheCoordinator.runFetchAndUpdateSparkAnonSetCacheForGroupId(
e,
electrumXClient,
cryptoCurrency.network,
),
);
@ -763,6 +769,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
...possibleFutures,
FiroCacheCoordinator.runFetchAndUpdateSparkUsedCoinTags(
electrumXClient,
cryptoCurrency.network,
),
]);
@ -782,11 +789,13 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
groupIdTimestampUTCMap[i.toString()] as int? ?? 0;
final info = await FiroCacheCoordinator.getLatestSetInfoForGroupId(
i,
cryptoCurrency.network,
);
final anonymitySetResult =
await FiroCacheCoordinator.getSetCoinsForGroupId(
i,
newerThanTimeStamp: lastCheckedTimeStampUTC,
network: cryptoCurrency.network,
);
final coinsRaw = anonymitySetResult
.map(
@ -882,7 +891,10 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
// only fetch tags from db if we need them to compare against any items
// in coinsToCheck
if (coinsToCheck.isNotEmpty) {
spentCoinTags = await FiroCacheCoordinator.getUsedCoinTags(0);
spentCoinTags = await FiroCacheCoordinator.getUsedCoinTags(
0,
cryptoCurrency.network,
);
}
// check and update coins if required
@ -992,6 +1004,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final pairs = await FiroCacheCoordinator.getUsedCoinTxidsFor(
tags: tags,
network: cryptoCurrency.network,
);
pairs.removeWhere((e) => usedCoinTxidsFoundLocally.contains(e.txid));

View file

@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
class Breathing extends StatefulWidget {
const Breathing({super.key, required this.child});
final Widget child;
@override
State<Breathing> createState() => _BreathingState();
}
class _BreathingState extends State<Breathing> {
bool _hovering = false;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(
() => _hovering = true,
),
onExit: (_) => setState(
() => _hovering = false,
),
child: AnimatedScale(
scale: _hovering ? 1.00 : 0.98,
duration: const Duration(
milliseconds: 200,
),
child: widget.child,
),
);
}
}

View file

@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import '../../utilities/util.dart';
import '../stack_dialog.dart';
class FiroExchangeAddressDialog extends StatelessWidget {
const FiroExchangeAddressDialog({super.key});
@override
Widget build(BuildContext context) {
return StackOkDialog(
title: "Firo exchange address detected",
message: "Sending to an exchange address from a Spark balance is not"
" allowed. Please send from your transparent balance.",
desktopPopRootNavigator: Util.isDesktop,
maxWidth: Util.isDesktop ? 500 : null,
);
}
}
Future<void> showFiroExchangeAddressWarning(
BuildContext context,
VoidCallback onClosed,
) async {
await showDialog<void>(
context: context,
builder: (_) => const FiroExchangeAddressDialog(),
);
onClosed();
}

View file

@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:share_plus/share_plus.dart';
import '../../../notifications/show_flush_bar.dart';
@ -17,6 +17,7 @@ import '../../../utilities/text_styles.dart';
import '../../../utilities/util.dart';
import '../../conditional_parent.dart';
import '../../desktop/secondary_button.dart';
import '../../qr.dart';
import '../../rounded_container.dart';
import '../../rounded_white_container.dart';
import '../simple_mobile_dialog.dart';
@ -154,18 +155,9 @@ class _FrostStepQrDialogState extends State<FrostStepQrDialog> {
padding: const EdgeInsets.all(16),
child: AspectRatio(
aspectRatio: 1,
child: QrImageView(
child: QR(
data: widget.data,
padding: EdgeInsets.zero,
foregroundColor: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
// dataModuleStyle: QrDataModuleStyle(
// dataModuleShape: QrDataModuleShape.square,
// color: Theme.of(context)
// .extension<StackColors>()!
// .accentColorDark,
// ),
),
),
),

27
lib/widgets/qr.dart Normal file
View file

@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
/// Centralised Qr code image widget
class QR extends StatelessWidget {
const QR({super.key, required this.data, this.size, this.padding});
final String data;
final double? size;
final EdgeInsets? padding;
@override
Widget build(BuildContext context) {
return QrImageView(
data: data,
size: size,
padding: padding ?? const EdgeInsets.all(10),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
// backgroundColor:
// Theme.of(context).extension<StackColors>()!.background,
// foregroundColor: Theme.of(context)
// .extension<StackColors>()!
// .accentColorDark,
);
}
}

View file

@ -188,14 +188,19 @@ class StackOkDialog extends StatelessWidget {
height: 8,
),
if (message != null)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
message!,
style: STextStyles.smallMed14(context),
),
],
ConstrainedBox(
constraints:
BoxConstraints(maxWidth: maxWidth ?? double.infinity),
child: Row(
children: [
Flexible(
child: Text(
message!,
style: STextStyles.smallMed14(context),
),
),
],
),
),
const SizedBox(
height: 20,

View file

@ -1807,8 +1807,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: f1d02f7ad489df3119a540a7f31485db6d837843
resolved-ref: f1d02f7ad489df3119a540a7f31485db6d837843
ref: "647cadc3c82c276dc07915b02d24538fd610f220"
resolved-ref: "647cadc3c82c276dc07915b02d24538fd610f220"
url: "https://github.com/cypherstack/tor.git"
source: git
version: "0.0.1"