implement optional import of flutter_libsparkmobile
What changed, and why it matters
This commit refactors how the Stack Wallet app uses the Firo Spark cryptography library. Instead of directly importing flutter_libsparkmobile everywhere, it introduces a generated interface wrapper so the library can be optionally included or excluded at build time. Most of the diff is code reformatting and replacing direct library calls with calls through the new wrapper. There is no obvious security bug in the diff itself, but the change touches sensitive wallet code (address generation, transaction signing, coin identification, fee estimation) and the generated template has placeholder error paths when Firo is not enabled.
Reviewers should verify that the generated wrapper faithfully forwards all parameters and return values to flutter_libsparkmobile without mutation, that the stub fallback cannot be reached in production builds, and that the moved logging bridge does not drop or misclassify security-relevant Spark library errors. Because the diff is dominated by formatting changes, a side-by-side functional-only comparison is recommended.
Security signals we found
Refactor of cryptographic library integration for Firo Spark (address generation, transaction creation, fee estimation, coin identification/recovery, tag hashing)
Introduction of build-time conditional implementation via code generation template with stub fallback that throws exceptions
Removal of direct flutter_libsparkmobile imports from wallet core and UI files; routing through generated interface
Logging bridge for Spark library logs moved into generated template
Large formatting-only changes make functional diffs harder to review
Evidence from the diff
The change introduces lib/wl_gen/interfaces/lib_spark_interface.dart, an abstract LibSparkInterface, plus a code-generation template tool/wl_templates/FIRO_lib_spark_interface_impl.template.dart that produces a real implementation backed by flutter_libsparkmobile when FIRO is enabled, or a stub that throws ‘FIRO not enabled!’ when disabled. Call sites in firo_cache.dart, firo_cache_writer.dart, spark name UI, spark_interface.dart, and logger.dart are updated to use the wrapper (libSpark, hashTags, etc.) instead of direct LibSpark/Log imports. The commit also removes the spark-specific logging extension from logger.dart and moves it into the generated template. The pubspec.lock change is a minor transitive dependency bump (build_cli_annotations 2.1.0 -> 2.1.1).
Changed components
lib/db/sqlite/firo_cache.dartlib/db/sqlite/firo_cache_writer.dartlib/pages/spark_names/buy_spark_name_view.dartlib/pages/spark_names/sub_widgets/buy_spark_name_option_widget.dartlib/utilities/logger.dartlib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dartlib/wl_gen/interfaces/lib_spark_interface.darttool/wl_templates/FIRO_lib_spark_interface_impl.template.dartpubspec.lockInspect captured patch +1047 / −609
diff --git a/lib/db/sqlite/firo_cache.dart b/lib/db/sqlite/firo_cache.dart
index 4478f42..9a0d83f 100644
--- a/lib/db/sqlite/firo_cache.dart
+++ b/lib/db/sqlite/firo_cache.dart
@@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:io';
import 'dart:isolate';
-import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
import 'package:mutex/mutex.dart';
import 'package:sqlite3/sqlite3.dart';
import 'package:uuid/uuid.dart';
@@ -13,6 +12,7 @@ import '../../utilities/extensions/extensions.dart';
import '../../utilities/logger.dart';
import '../../utilities/stack_file_system.dart';
import '../../wallets/crypto_currency/crypto_currency.dart';
+import '../../wl_gen/interfaces/lib_spark_interface.dart';
part 'firo_cache_coordinator.dart';
part 'firo_cache_reader.dart';
@@ -30,12 +30,12 @@ abstract class _FiroCache {
static String sparkSetCacheFileName(CryptoCurrencyNetwork network) =>
network == CryptoCurrencyNetwork.main
- ? "spark_set_v$_setCacheVersion.sqlite3"
- : "spark_set_v${_setCacheVersion}_${network.name}.sqlite3";
+ ? "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";
+ ? "spark_tags_v$_tagsCacheVersion.sqlite3"
+ : "spark_tags_v${_tagsCacheVersion}_${network.name}.sqlite3";
static final Map<CryptoCurrencyNetwork, Database> _setCacheDB = {};
static final Map<CryptoCurrencyNetwork, Database> _usedTagsCacheDB = {};
diff --git a/lib/db/sqlite/firo_cache_writer.dart b/lib/db/sqlite/firo_cache_writer.dart
index 0938360..fadc3eb 100644
--- a/lib/db/sqlite/firo_cache_writer.dart
+++ b/lib/db/sqlite/firo_cache_writer.dart
@@ -15,10 +15,9 @@ class FCResult {
/// returns true if successful, otherwise some exception
FCResult _updateSparkUsedTagsWith(Database db, List<List<dynamic>> tags) {
// hash the tags here since this function is called in a background isolate
- final hashedTags =
- LibSpark.hashTags(
- base64Tags: tags.map((e) => e[0] as String).toList(),
- ).toList();
+ final hashedTags = hashTags(
+ base64Tags: tags.map((e) => e[0] as String).toList(),
+ ).toList();
if (hashedTags.isEmpty) {
// nothing to add, return early
return FCResult(success: true);
diff --git a/lib/pages/spark_names/buy_spark_name_view.dart b/lib/pages/spark_names/buy_spark_name_view.dart
index b05d6b2..478893d 100644
--- a/lib/pages/spark_names/buy_spark_name_view.dart
+++ b/lib/pages/spark_names/buy_spark_name_view.dart
@@ -3,7 +3,6 @@ import 'dart:async';
import 'package:decimal/decimal.dart';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:isar_community/isar.dart';
@@ -32,6 +31,7 @@ import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../widgets/custom_buttons/blue_text_button.dart';
import '../../widgets/dialogs/s_dialog.dart';
import '../../widgets/rounded_white_container.dart';
+import '../../wl_gen/interfaces/lib_spark_interface.dart';
import 'confirm_spark_name_transaction_view.dart';
class BuySparkNameView extends ConsumerStatefulWidget {
@@ -108,16 +108,15 @@ class _BuySparkNameViewState extends ConsumerState<BuySparkNameView> {
throw Exception("Invalid Spark address selected");
}
- final myAddresses =
- await wallet.mainDB.isar.addresses
- .where()
- .walletIdEqualTo(widget.walletId)
- .filter()
- .typeEqualTo(AddressType.spark)
- .and()
- .subTypeEqualTo(AddressSubType.receiving)
- .valueProperty()
- .findAll();
+ final myAddresses = await wallet.mainDB.isar.addresses
+ .where()
+ .walletIdEqualTo(widget.walletId)
+ .filter()
+ .typeEqualTo(AddressType.spark)
+ .and()
+ .subTypeEqualTo(AddressSubType.receiving)
+ .valueProperty()
+ .findAll();
if (!myAddresses.contains(chosenAddress)) {
throw Exception("Selected Spark address does not belong to this wallet");
@@ -137,30 +136,28 @@ class _BuySparkNameViewState extends ConsumerState<BuySparkNameView> {
if (_preRegLock) return;
_preRegLock = true;
try {
- final txData =
- (await showLoading(
- whileFuture: _preRegFuture(),
- context: context,
- message: "Preparing transaction...",
- onException: (e) {
- throw e;
- },
- ))!;
+ final txData = (await showLoading(
+ whileFuture: _preRegFuture(),
+ context: context,
+ message: "Preparing transaction...",
+ onException: (e) {
+ throw e;
+ },
+ ))!;
if (mounted) {
if (Util.isDesktop) {
await showDialog<void>(
context: context,
- builder:
- (context) => SDialog(
- child: SizedBox(
- width: 580,
- child: ConfirmSparkNameTransactionView(
- txData: txData,
- walletId: widget.walletId,
- ),
- ),
+ builder: (context) => SDialog(
+ child: SizedBox(
+ width: 580,
+ child: ConfirmSparkNameTransactionView(
+ txData: txData,
+ walletId: widget.walletId,
),
+ ),
+ ),
);
} else {
await Navigator.of(context).pushNamed(
@@ -180,13 +177,12 @@ class _BuySparkNameViewState extends ConsumerState<BuySparkNameView> {
await showDialog<void>(
context: context,
- builder:
- (_) => StackOkDialog(
- title: "Error",
- message: err,
- desktopPopRootNavigator: Util.isDesktop,
- maxWidth: Util.isDesktop ? 600 : null,
- ),
+ builder: (_) => StackOkDialog(
+ title: "Error",
+ message: err,
+ desktopPopRootNavigator: Util.isDesktop,
+ maxWidth: Util.isDesktop ? 600 : null,
+ ),
);
}
} finally {
@@ -260,10 +256,9 @@ class _BuySparkNameViewState extends ConsumerState<BuySparkNameView> {
);
},
child: Column(
- crossAxisAlignment:
- Util.isDesktop
- ? CrossAxisAlignment.start
- : CrossAxisAlignment.stretch,
+ crossAxisAlignment: Util.isDesktop
+ ? CrossAxisAlignment.start
+ : CrossAxisAlignment.stretch,
children: [
RoundedWhiteContainer(
padding: EdgeInsets.all(Util.isDesktop ? 0 : 12),
@@ -272,27 +267,23 @@ class _BuySparkNameViewState extends ConsumerState<BuySparkNameView> {
children: [
Text(
"Name",
- style:
- Util.isDesktop
- ? STextStyles.w500_14(context).copyWith(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.infoItemLabel,
- )
- : STextStyles.w500_12(context).copyWith(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.infoItemLabel,
- ),
+ style: Util.isDesktop
+ ? STextStyles.w500_14(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.infoItemLabel,
+ )
+ : STextStyles.w500_12(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.infoItemLabel,
+ ),
),
Text(
widget.name,
- style:
- Util.isDesktop
- ? STextStyles.w500_14(context)
- : STextStyles.w500_12(context),
+ style: Util.isDesktop
+ ? STextStyles.w500_14(context)
+ : STextStyles.w500_12(context),
),
],
),
@@ -307,20 +298,17 @@ class _BuySparkNameViewState extends ConsumerState<BuySparkNameView> {
children: [
Text(
"Spark address",
- style:
- Util.isDesktop
- ? STextStyles.w500_14(context).copyWith(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.infoItemLabel,
- )
- : STextStyles.w500_12(context).copyWith(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.infoItemLabel,
- ),
+ style: Util.isDesktop
+ ? STextStyles.w500_14(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.infoItemLabel,
+ )
+ : STextStyles.w500_12(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.infoItemLabel,
+ ),
),
CustomTextButton(
text: "Use current",
@@ -361,20 +349,17 @@ class _BuySparkNameViewState extends ConsumerState<BuySparkNameView> {
children: [
Text(
"Additional info",
- style:
- Util.isDesktop
- ? STextStyles.w500_14(context).copyWith(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.infoItemLabel,
- )
- : STextStyles.w500_12(context).copyWith(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.infoItemLabel,
- ),
+ style: Util.isDesktop
+ ? STextStyles.w500_14(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.infoItemLabel,
+ )
+ : STextStyles.w500_12(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.infoItemLabel,
+ ),
),
],
),
@@ -410,20 +395,17 @@ class _BuySparkNameViewState extends ConsumerState<BuySparkNameView> {
children: [
Text(
"${isRenewal ? "Renew" : "Register"} for",
- style:
- Util.isDesktop
- ? STextStyles.w500_14(context).copyWith(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.infoItemLabel,
- )
- : STextStyles.w500_12(context).copyWith(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.infoItemLabel,
- ),
+ style: Util.isDesktop
+ ? STextStyles.w500_14(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.infoItemLabel,
+ )
+ : STextStyles.w500_12(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.infoItemLabel,
+ ),
),
SizedBox(
width: Util.isDesktop ? 180 : 140,
@@ -451,10 +433,9 @@ class _BuySparkNameViewState extends ConsumerState<BuySparkNameView> {
isExpanded: true,
buttonStyleData: ButtonStyleData(
decoration: BoxDecoration(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.textFieldDefaultBG,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
@@ -467,10 +448,9 @@ class _BuySparkNameViewState extends ConsumerState<BuySparkNameView> {
Assets.svg.chevronDown,
width: 12,
height: 6,
- color:
- Theme.of(context)
- .extension<StackColors>()!
- .textFieldActiveSearchIconRight,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .textFieldActiveSearchIconRight,
),
),
),
@@ -479,10 +459,9 @@ class _BuySparkNameViewState extends ConsumerState<BuySparkNameView> {
elevation: 0,
maxHeight: 250,
decoration: BoxDecoration(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.textFieldDefaultBG,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
@@ -508,20 +487,17 @@ class _BuySparkNameViewState extends ConsumerState<BuySparkNameView> {
children: [
Text(
"Cost",
- style:
- Util.isDesktop
- ? STextStyles.w500_14(context).copyWith(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.infoItemLabel,
- )
- : STextStyles.w500_12(context).copyWith(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.infoItemLabel,
- ),
+ style: Util.isDesktop
+ ? STextStyles.w500_14(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.infoItemLabel,
+ )
+ : STextStyles.w500_12(context).copyWith(
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.infoItemLabel,
+ ),
),
Text(
ref
@@ -529,15 +505,15 @@ class _BuySparkNameViewState extends ConsumerState<BuySparkNameView> {
.format(
Amount.fromDecimal(
Decimal.fromInt(
- kStandardSparkNamesFee[widget.name.length] * _years,
+ libSpark.standardSparkNamesFee[widget.name.length] *
+ _years,
),
fractionDigits: coin.fractionDigits,
),
),
- style:
- Util.isDesktop
- ? STextStyles.w500_14(context)
- : STextStyles.w500_12(context),
+ style: Util.isDesktop
+ ? STextStyles.w500_14(context)
+ : STextStyles.w500_12(context),
),
],
),
diff --git a/lib/pages/spark_names/sub_widgets/buy_spark_name_option_widget.dart b/lib/pages/spark_names/sub_widgets/buy_spark_name_option_widget.dart
index cc7fcfa..61c35fe 100644
--- a/lib/pages/spark_names/sub_widgets/buy_spark_name_option_widget.dart
+++ b/lib/pages/spark_names/sub_widgets/buy_spark_name_option_widget.dart
@@ -3,7 +3,6 @@ import 'dart:async';
import 'package:drift/drift.dart' as drift;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
-import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
@@ -22,6 +21,7 @@ import '../../../widgets/desktop/secondary_button.dart';
import '../../../widgets/dialogs/s_dialog.dart';
import '../../../widgets/rounded_white_container.dart';
import '../../../widgets/stack_dialog.dart';
+import '../../../wl_gen/interfaces/lib_spark_interface.dart';
import '../buy_spark_name_view.dart';
class BuySparkNameOptionWidget extends ConsumerStatefulWidget {
@@ -57,9 +57,9 @@ class _BuySparkNameWidgetState extends ConsumerState<BuySparkNameOptionWidget> {
rethrow;
}
final db = ref.read(pDrift(widget.walletId));
- final results =
- await (db.select(db.sparkNames)
- ..where((e) => e.name.lower().equals(name))).get();
+ final results = await (db.select(
+ db.sparkNames,
+ )..where((e) => e.name.lower().equals(name))).get();
if (results.isNotEmpty) {
return false;
@@ -109,12 +109,11 @@ class _BuySparkNameWidgetState extends ConsumerState<BuySparkNameOptionWidget> {
if (mounted) {
await showDialog<void>(
context: context,
- builder:
- (_) => StackOkDialog(
- title: message,
- desktopPopRootNavigator: Util.isDesktop,
- maxWidth: Util.isDesktop ? 600 : null,
- ),
+ builder: (_) => StackOkDialog(
+ title: message,
+ desktopPopRootNavigator: Util.isDesktop,
+ maxWidth: Util.isDesktop ? 600 : null,
+ ),
);
}
} finally {
@@ -142,8 +141,9 @@ class _BuySparkNameWidgetState extends ConsumerState<BuySparkNameOptionWidget> {
@override
Widget build(BuildContext context) {
return Column(
- crossAxisAlignment:
- Util.isDesktop ? CrossAxisAlignment.start : CrossAxisAlignment.center,
+ crossAxisAlignment: Util.isDesktop
+ ? CrossAxisAlignment.start
+ : CrossAxisAlignment.center,
children: [
SizedBox(
height: 48,
@@ -154,10 +154,9 @@ class _BuySparkNameWidgetState extends ConsumerState<BuySparkNameOptionWidget> {
height: 48,
width: 100,
decoration: BoxDecoration(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.textFieldDefaultBG,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textFieldDefaultBG,
borderRadius: BorderRadius.all(
Radius.circular(Constants.size.circularBorderRadius),
),
@@ -168,7 +167,9 @@ class _BuySparkNameWidgetState extends ConsumerState<BuySparkNameOptionWidget> {
Expanded(
child: TextField(
inputFormatters: [
- LengthLimitingTextInputFormatter(kMaxNameLength),
+ LengthLimitingTextInputFormatter(
+ libSpark.maxNameLength,
+ ),
],
textInputAction: TextInputAction.search,
focusNode: _nameFieldFocus,
@@ -183,10 +184,9 @@ class _BuySparkNameWidgetState extends ConsumerState<BuySparkNameOptionWidget> {
Assets.svg.search,
width: 20,
height: 20,
- color:
- Theme.of(context)
- .extension<StackColors>()!
- .textFieldDefaultSearchIconLeft,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .textFieldDefaultSearchIconLeft,
),
),
fillColor: Colors.transparent,
@@ -206,7 +206,9 @@ class _BuySparkNameWidgetState extends ConsumerState<BuySparkNameOptionWidget> {
setState(() {
_isInvalidCharacters =
value.isNotEmpty &&
- !RegExp(kNameRegexString).hasMatch(value);
+ !RegExp(
+ libSpark.nameRegexString,
+ ).hasMatch(value);
});
},
),
@@ -220,10 +222,9 @@ class _BuySparkNameWidgetState extends ConsumerState<BuySparkNameOptionWidget> {
),
const SizedBox(height: 4),
Row(
- mainAxisAlignment:
- _isInvalidCharacters
- ? MainAxisAlignment.spaceBetween
- : MainAxisAlignment.end,
+ mainAxisAlignment: _isInvalidCharacters
+ ? MainAxisAlignment.spaceBetween
+ : MainAxisAlignment.end,
children: [
if (_isInvalidCharacters)
Text(
@@ -238,12 +239,11 @@ class _BuySparkNameWidgetState extends ConsumerState<BuySparkNameOptionWidget> {
builder: (context) {
final length = _nameController.text.length;
return Text(
- "$length/$kMaxNameLength",
+ "$length/${libSpark.maxNameLength}",
style: STextStyles.w500_10(context).copyWith(
- color:
- Theme.of(
- context,
- ).extension<StackColors>()!.textSubtitle2,
+ color: Theme.of(
+ context,
+ ).extension<StackColors>()!.textSubtitle2,
),
);
},
@@ -256,7 +256,7 @@ class _BuySparkNameWidgetState extends ConsumerState<BuySparkNameOptionWidget> {
label: "Lookup",
enabled:
_nameController.text.isNotEmpty &&
- RegExp(kNameRegexString).hasMatch(_nameController.text),
+ RegExp(libSpark.nameRegexString).hasMatch(_nameController.text),
buttonHeight: Util.isDesktop ? ButtonHeight.l : null,
onPressed: _lookup,
),
@@ -287,15 +287,13 @@ class _NameCard extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final availability = isAvailable ? "Available" : "Unavailable";
- final color =
- isAvailable
- ? Theme.of(context).extension<StackColors>()!.accentColorGreen
- : Theme.of(context).extension<StackColors>()!.accentColorRed;
+ final color = isAvailable
+ ? Theme.of(context).extension<StackColors>()!.accentColorGreen
+ : Theme.of(context).extension<StackColors>()!.accentColorRed;
- final style =
- (Util.isDesktop
- ? STextStyles.w500_16(context)
- : STextStyles.w500_12(context));
+ final style = (Util.isDesktop
+ ? STextStyles.w500_16(context)
+ : STextStyles.w500_12(context));
return RoundedWhiteContainer(
padding: EdgeInsets.all(Util.isDesktop ? 24 : 16),
@@ -321,51 +319,49 @@ class _NameCard extends ConsumerWidget {
PrimaryButton(
label: "Buy name",
enabled: isAvailable,
- buttonHeight:
- Util.isDesktop ? ButtonHeight.m : ButtonHeight.l,
+ buttonHeight: Util.isDesktop
+ ? ButtonHeight.m
+ : ButtonHeight.l,
width: Util.isDesktop ? 140 : 120,
onPressed: () async {
if (context.mounted) {
if (Util.isDesktop) {
await showDialog<void>(
context: context,
- builder:
- (context) => SDialog(
- child: SizedBox(
- width: 580,
- child: Column(
+ builder: (context) => SDialog(
+ child: SizedBox(
+ width: 580,
+ child: Column(
+ children: [
+ Row(
+ mainAxisAlignment:
+ MainAxisAlignment.spaceBetween,
children: [
- Row(
- mainAxisAlignment:
- MainAxisAlignment.spaceBetween,
- children: [
- Padding(
- padding: const EdgeInsets.only(
- left: 32,
- ),
- child: Text(
- "Buy name",
- style: STextStyles.desktopH3(
- context,
- ),
- ),
- ),
- const DesktopDialogCloseButton(),
- ],
- ),
Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 32,
+ padding: const EdgeInsets.only(
+ left: 32,
),
- child: BuySparkNameView(
- walletId: walletId,
- name: name,
+ child: Text(
+ "Buy name",
+ style: STextStyles.desktopH3(context),
),
),
+ const DesktopDialogCloseButton(),
],
),
- ),
+ Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 32,
+ ),
+ child: BuySparkNameView(
+ walletId: walletId,
+ name: name,
+ ),
+ ),
+ ],
),
+ ),
+ ),
);
} else {
await Navigator.of(context).pushNamed(
diff --git a/lib/utilities/logger.dart b/lib/utilities/logger.dart
index d4cc03d..b5ae5f0 100644
--- a/lib/utilities/logger.dart
+++ b/lib/utilities/logger.dart
@@ -14,7 +14,6 @@ import 'dart:core';
import 'dart:isolate';
import 'dart:ui';
-import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart' as spark;
import 'package:logger/logger.dart';
import 'util.dart';
@@ -23,26 +22,6 @@ export 'enums/log_level_enum.dart';
const _kLoggerPortName = "logger_port";
-// convenience conversion for spark
-extension LoggingLevelExt on spark.LoggingLevel {
- Level getLoggerLevel() {
- switch (this) {
- case spark.LoggingLevel.info:
- return Level.info;
- case spark.LoggingLevel.warning:
- return Level.warning;
- case spark.LoggingLevel.error:
- return Level.error;
- case spark.LoggingLevel.fatal:
- return Level.fatal;
- case spark.LoggingLevel.debug:
- return Level.debug;
- case spark.LoggingLevel.trace:
- return Level.trace;
- }
- }
-}
-
class Logging {
Logging._();
static final Logging _instance = Logging._();
@@ -82,8 +61,9 @@ class Logging {
PrettyPrinter prettyPrinter(bool toFile) => PrettyPrinter(
printEmojis: false,
methodCount: 0,
- dateTimeFormat:
- toFile ? DateTimeFormat.none : DateTimeFormat.dateAndTime,
+ dateTimeFormat: toFile
+ ? DateTimeFormat.none
+ : DateTimeFormat.dateAndTime,
colors: !toFile,
noBoxingByDefault: toFile,
);
@@ -132,8 +112,8 @@ class Logging {
String _stringifyMessage(dynamic message) =>
!(message is Map || message is Iterable)
- ? message.toString()
- : JsonEncoder.withIndent(' ', (o) => o.toString()).convert(message);
+ ? message.toString()
+ : JsonEncoder.withIndent(' ', (o) => o.toString()).convert(message);
void log(
Level level,
diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart
index a008d1b..d9059a5 100644
--- a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart
+++ b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart
@@ -5,10 +5,6 @@ import 'dart:math';
import 'package:bitcoindart/bitcoindart.dart' as btc;
import 'package:decimal/decimal.dart';
import 'package:flutter/foundation.dart';
-import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart'
- as spark
- show Log;
-import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
import 'package:isar_community/isar.dart';
import 'package:logger/logger.dart';
@@ -28,6 +24,7 @@ import '../../../utilities/enums/derive_path_type_enum.dart';
import '../../../utilities/extensions/extensions.dart';
import '../../../utilities/logger.dart';
import '../../../utilities/prefs.dart';
+import '../../../wl_gen/interfaces/lib_spark_interface.dart';
import '../../crypto_currency/crypto_currency.dart';
import '../../crypto_currency/interfaces/electrumx_currency_interface.dart';
import '../../isar/models/spark_coin.dart';
@@ -55,25 +52,11 @@ String _hashTag(String tag) {
final x = components[0].substring(1);
final y = components[1].substring(0, components[1].length - 1);
- final hash = LibSpark.hashTag(x, y);
+ final hash = libSpark.hashTag(x, y);
return hash;
}
-void initSparkLogging(Level level) {
- final levels = Level.values.where((e) => e >= level).map((e) => e.name);
- spark.Log.levels.addAll(
- LoggingLevel.values.where((e) => levels.contains(e.name)),
- );
- spark.Log.onLog = (level, value, {error, stackTrace, required time}) {
- Logging.instance.log(
- level.getLoggerLevel(),
- value,
- error: error,
- stackTrace: stackTrace,
- time: time,
- );
- };
-}
+void initSparkLogging(Level level) => libSpark.initSparkLogging(level);
abstract class _SparkIsolate {
static Isolate? _isolate;
@@ -135,7 +118,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
static bool validateSparkAddress({
required String address,
required bool isTestNet,
- }) => LibSpark.validateAddress(address: address, isTestNet: isTestNet);
+ }) => libSpark.validateAddress(address: address, isTestNet: isTestNet);
Future<String> hashTag(String tag) async {
try {
@@ -174,16 +157,17 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final String derivationPath;
if (cryptoCurrency.network.isTestNet) {
derivationPath =
- "$kSparkBaseDerivationPathTestnet$kDefaultSparkIndex";
+ "${libSpark.sparkBaseDerivationPathTestnet}$kDefaultSparkIndex";
} else {
- derivationPath = "$kSparkBaseDerivationPath$kDefaultSparkIndex";
+ derivationPath =
+ "${libSpark.sparkBaseDerivationPath}$kDefaultSparkIndex";
}
final keys = root.derivePath(derivationPath);
- _sparkChangeAddressCached = await LibSpark.getAddress(
+ _sparkChangeAddressCached = await libSpark.getAddress(
privateKey: keys.privateKey.data,
index: kDefaultSparkIndex,
- diversifier: kSparkChange,
+ diversifier: libSpark.sparkChange,
isTestNet: cryptoCurrency.network.isTestNet,
);
}
@@ -202,20 +186,19 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
@override
Future<List<Address>> fetchAddressesForElectrumXScan() async {
- final allAddresses =
- await mainDB
- .getAddresses(walletId)
- .filter()
- .not()
- .group(
- (q) => q
- .typeEqualTo(AddressType.spark)
- .or()
- .typeEqualTo(AddressType.nonWallet)
- .or()
- .subTypeEqualTo(AddressSubType.nonWallet),
- )
- .findAll();
+ final allAddresses = await mainDB
+ .getAddresses(walletId)
+ .filter()
+ .not()
+ .group(
+ (q) => q
+ .typeEqualTo(AddressType.spark)
+ .or()
+ .typeEqualTo(AddressType.nonWallet)
+ .or()
+ .subTypeEqualTo(AddressSubType.nonWallet),
+ )
+ .findAll();
return allAddresses;
}
@@ -236,20 +219,21 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
// default to starting at 1 if none found
int diversifier = (highestStoredDiversifier ?? 0) + 1;
// change address check
- if (diversifier == kSparkChange) {
+ if (diversifier == libSpark.sparkChange) {
diversifier++;
}
final root = await getRootHDNode();
final String derivationPath;
if (cryptoCurrency.network.isTestNet) {
- derivationPath = "$kSparkBaseDerivationPathTestnet$kDefaultSparkIndex";
+ derivationPath =
+ "${libSpark.sparkBaseDerivationPathTestnet}$kDefaultSparkIndex";
} else {
- derivationPath = "$kSparkBaseDerivationPath$kDefaultSparkIndex";
+ derivationPath = "${libSpark.sparkBaseDerivationPath}$kDefaultSparkIndex";
}
final keys = root.derivePath(derivationPath);
- final String addressString = await LibSpark.getAddress(
+ final String addressString = await libSpark.getAddress(
privateKey: keys.privateKey.data,
index: kDefaultSparkIndex,
diversifier: diversifier,
@@ -276,18 +260,17 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
);
} else {
// fetch spendable spark coins
- final coins =
- await mainDB.isar.sparkCoins
- .where()
- .walletIdEqualToAnyLTagHash(walletId)
- .filter()
- .isUsedEqualTo(false)
- .and()
- .heightIsNotNull()
- .and()
- .not()
- .valueIntStringEqualTo("0")
- .findAll();
+ final coins = await mainDB.isar.sparkCoins
+ .where()
+ .walletIdEqualToAnyLTagHash(walletId)
+ .filter()
+ .isUsedEqualTo(false)
+ .and()
+ .heightIsNotNull()
+ .and()
+ .not()
+ .valueIntStringEqualTo("0")
+ .findAll();
final available = coins
.map((e) => e.value)
@@ -301,24 +284,25 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
}
// prepare coin data for ffi
- final serializedCoins =
- coins
- .map(
- (e) => (
- serializedCoin: e.serializedCoinB64!,
- serializedCoinContext: e.contextB64!,
- groupId: e.groupId,
- height: e.height!,
- ),
- )
- .toList();
+ final serializedCoins = coins
+ .map(
+ (e) => (
+ serializedCoin: e.serializedCoinB64!,
+ serializedCoinContext: e.contextB64!,
+ groupId: e.groupId,
+ height: e.height!,
+ ),
+ )
+ .toList();
final root = await getRootHDNode();
final String derivationPath;
if (cryptoCurrency.network.isTestNet) {
- derivationPath = "$kSparkBaseDerivationPathTestnet$kDefaultSparkIndex";
+ derivationPath =
+ "${libSpark.sparkBaseDerivationPathTestnet}$kDefaultSparkIndex";
} else {
- derivationPath = "$kSparkBaseDerivationPath$kDefaultSparkIndex";
+ derivationPath =
+ "${libSpark.sparkBaseDerivationPath}$kDefaultSparkIndex";
}
final privateKey = root.derivePath(derivationPath).privateKey.data;
int estimate = await _asyncSparkFeesWrapper(
@@ -393,18 +377,17 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final txAmount = transparentSumOut + sparkSumOut;
// fetch spendable spark coins
- final coins =
- await mainDB.isar.sparkCoins
- .where()
- .walletIdEqualToAnyLTagHash(walletId)
- .filter()
- .isUsedEqualTo(false)
- .and()
- .heightIsNotNull()
- .and()
- .not()
- .valueIntStringEqualTo("0")
- .findAll();
+ final coins = await mainDB.isar.sparkCoins
+ .where()
+ .walletIdEqualToAnyLTagHash(walletId)
+ .filter()
+ .isUsedEqualTo(false)
+ .and()
+ .heightIsNotNull()
+ .and()
+ .not()
+ .valueIntStringEqualTo("0")
+ .findAll();
final available = info.cachedBalanceTertiary.spendable;
@@ -415,17 +398,16 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final bool isSendAll = available == txAmount;
// prepare coin data for ffi
- final serializedCoins =
- coins
- .map(
- (e) => (
- serializedCoin: e.serializedCoinB64!,
- serializedCoinContext: e.contextB64!,
- groupId: e.groupId,
- height: e.height!,
- ),
- )
- .toList();
+ final serializedCoins = coins
+ .map(
+ (e) => (
+ serializedCoin: e.serializedCoinB64!,
+ serializedCoinContext: e.contextB64!,
+ groupId: e.groupId,
+ height: e.height!,
+ ),
+ )
+ .toList();
final currentId = await electrumXClient.getSparkLatestCoinId();
final List<Map<String, dynamic>> setMaps = [];
@@ -451,8 +433,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
"blockHash": info.blockHash,
"setHash": info.setHash,
"coinGroupID": i,
- "coins":
- resultSet.map((e) => [e.serialized, e.txHash, e.context]).toList(),
+ "coins": resultSet
+ .map((e) => [e.serialized, e.txHash, e.context])
+ .toList(),
};
setData["coinGroupID"] = i;
@@ -463,31 +446,28 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
));
}
- final allAnonymitySets =
- setMaps
- .map(
- (e) => (
- setId: e["coinGroupID"] as int,
- setHash: e["setHash"] as String,
- set:
- (e["coins"] as List)
- .map(
- (e) => (
- serializedCoin: e[0] as String,
- txHash: e[1] as String,
- ),
- )
- .toList(),
- ),
- )
- .toList();
+ final allAnonymitySets = setMaps
+ .map(
+ (e) => (
+ setId: e["coinGroupID"] as int,
+ setHash: e["setHash"] as String,
+ set: (e["coins"] as List)
+ .map(
+ (e) =>
+ (serializedCoin: e[0] as String, txHash: e[1] as String),
+ )
+ .toList(),
+ ),
+ )
+ .toList();
final root = await getRootHDNode();
final String derivationPath;
if (cryptoCurrency.network.isTestNet) {
- derivationPath = "$kSparkBaseDerivationPathTestnet$kDefaultSparkIndex";
+ derivationPath =
+ "${libSpark.sparkBaseDerivationPathTestnet}$kDefaultSparkIndex";
} else {
- derivationPath = "$kSparkBaseDerivationPath$kDefaultSparkIndex";
+ derivationPath = "${libSpark.sparkBaseDerivationPath}$kDefaultSparkIndex";
}
final privateKey = root.derivePath(derivationPath).privateKey.data;
@@ -500,7 +480,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
sparkRecipientsWithFeeSubtracted;
final recipientCount =
(txData.recipients?.where((e) => e.amount.raw > BigInt.zero).length ??
- 0);
+ 0);
final totalRecipientCount =
recipientCount + (txData.sparkRecipients?.length ?? 0);
final BigInt estimatedFee;
@@ -620,7 +600,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
({Uint8List script, int size})? noProofNameTxData;
if (txData.sparkNameInfo != null) {
- noProofNameTxData = LibSpark.createSparkNameScript(
+ noProofNameTxData = libSpark.createSparkNameScript(
sparkNameValidityBlocks: txData.sparkNameInfo!.validBlocks,
name: txData.sparkNameInfo!.name,
additionalInfo: txData.sparkNameInfo!.additionalInfo,
@@ -662,15 +642,13 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
[],
serializedCoins: serializedCoins,
allAnonymitySets: allAnonymitySets,
- idAndBlockHashes:
- idAndBlockHashes
- .map(
- (e) => (setId: e.groupId, blockHash: base64Decode(e.blockHash)),
- )
- .toList(),
+ idAndBlockHashes: idAndBlockHashes
+ .map((e) => (setId: e.groupId, blockHash: base64Decode(e.blockHash)))
+ .toList(),
txHash: extractedTx.getHash(),
- additionalTxSize:
- txData.sparkNameInfo == null ? 0 : noProofNameTxData!.size,
+ additionalTxSize: txData.sparkNameInfo == null
+ ? 0
+ : noProofNameTxData!.size,
));
for (final outputScript in spend.outputScripts) {
@@ -695,7 +673,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
int hashFailSafe = 0;
while (nameScriptData == null) {
try {
- nameScriptData = LibSpark.createSparkNameScript(
+ nameScriptData = libSpark.createSparkNameScript(
sparkNameValidityBlocks: txData.sparkNameInfo!.validBlocks,
name: txData.sparkNameInfo!.name,
additionalInfo: txData.sparkNameInfo!.additionalInfo,
@@ -745,11 +723,10 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
sequence: 0xffffffff,
outpoint: null,
addresses: [],
- valueStringSats:
- tempOutputs
- .map((e) => e.value)
- .fold(fee.raw, (p, e) => p + e)
- .toString(),
+ valueStringSats: tempOutputs
+ .map((e) => e.value)
+ .fold(fee.raw, (p, e) => p + e)
+ .toString(),
witness: null,
innerRedeemScriptAsm: null,
coinbase: null,
@@ -792,10 +769,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
timestamp: DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
inputs: List.unmodifiable(tempInputs),
outputs: List.unmodifiable(tempOutputs),
- type:
- tempOutputs.map((e) => e.walletOwns).fold(true, (p, e) => p &= e)
- ? TransactionType.sentToSelf
- : TransactionType.outgoing,
+ type: tempOutputs.map((e) => e.walletOwns).fold(true, (p, e) => p &= e)
+ ? TransactionType.sentToSelf
+ : TransactionType.outgoing,
subType: TransactionSubType.sparkSpend,
otherData: jsonEncode({"overrideFee": fee.toJsonString()}),
height: null,
@@ -962,10 +938,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
+
1; // update balance
- final percentIncrement =
- refreshProgressRange == null
- ? null
- : (refreshProgressRange.$2 - refreshProgressRange.$1) / steps;
+ final percentIncrement = refreshProgressRange == null
+ ? null
+ : (refreshProgressRange.$2 - refreshProgressRange.$1) / steps;
double currentPercent = refreshProgressRange?.$1 ?? 0;
// fetch and update process for each set groupId as required
@@ -1025,10 +1000,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
afterBlockHash: lastCheckedHash,
network: cryptoCurrency.network,
);
- final coinsRaw =
- anonymitySetResult
- .map((e) => [e.serialized, e.txHash, e.context])
- .toList();
+ final coinsRaw = anonymitySetResult
+ .map((e) => [e.serialized, e.txHash, e.context])
+ .toList();
if (coinsRaw.isNotEmpty) {
rawCoinsBySetId[i] = coinsRaw;
@@ -1044,25 +1018,19 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
// get address(es) to get the private key hex strings required for
// identifying spark coins
- final sparkAddresses =
- await mainDB.isar.addresses
- .where()
- .walletIdEqualTo(walletId)
- .filter()
- .typeEqualTo(AddressType.spark)
- .findAll();
+ final sparkAddresses = await mainDB.isar.addresses
+ .where()
+ .walletIdEqualTo(walletId)
+ .filter()
+ .typeEqualTo(AddressType.spark)
+ .findAll();
final root = await getRootHDNode();
- final Set<String> privateKeyHexSet =
- sparkAddresses
- .map(
- (e) =>
- root
- .derivePath(e.derivationPath!.value)
- .privateKey
- .data
- .toHex,
- )
- .toSet();
+ final Set<String> privateKeyHexSet = sparkAddresses
+ .map(
+ (e) =>
+ root.derivePath(e.derivationPath!.value).privateKey.data.toHex,
+ )
+ .toSet();
// try to identify any coins in the unchecked set data
final List<SparkCoin> newlyIdCoins = [];
@@ -1108,15 +1076,14 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
}
// get unused and or unconfirmed coins from db
- final coinsToCheck =
- await mainDB.isar.sparkCoins
- .where()
- .walletIdEqualToAnyLTagHash(walletId)
- .filter()
- .heightIsNull()
- .or()
- .isUsedEqualTo(false)
- .findAll();
+ final coinsToCheck = await mainDB.isar.sparkCoins
+ .where()
+ .walletIdEqualToAnyLTagHash(walletId)
+ .filter()
+ .heightIsNull()
+ .or()
+ .isUsedEqualTo(false)
+ .findAll();
List<String>? spentCoinTags;
// only fetch tags from db if we need them to compare against any items
@@ -1147,10 +1114,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
checked = coin;
}
} else {
- checked =
- spentCoinTags!.contains(coin.lTagHash)
- ? coin.copyWith(isUsed: true)
- : coin;
+ checked = spentCoinTags!.contains(coin.lTagHash)
+ ? coin.copyWith(isUsed: true)
+ : coin;
}
checkedCoins.add(checked);
@@ -1170,13 +1136,12 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final currentHeight = await chainHeight;
// get all unused coins to update wallet spark balance
- final unusedCoins =
- await mainDB.isar.sparkCoins
- .where()
- .walletIdEqualToAnyLTagHash(walletId)
- .filter()
- .isUsedEqualTo(false)
- .findAll();
+ final unusedCoins = await mainDB.isar.sparkCoins
+ .where()
+ .walletIdEqualToAnyLTagHash(walletId)
+ .filter()
+ .isUsedEqualTo(false)
+ .findAll();
final sparkNamesUpdateFuture = refreshSparkNames();
@@ -1229,14 +1194,13 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
}
Future<Set<LTagPair>> getSparkSpendTransactionIds() async {
- final tags =
- await mainDB.isar.sparkCoins
- .where()
- .walletIdEqualToAnyLTagHash(walletId)
- .filter()
- .isUsedEqualTo(true)
- .lTagHashProperty()
- .findAll();
+ final tags = await mainDB.isar.sparkCoins
+ .where()
+ .walletIdEqualToAnyLTagHash(walletId)
+ .filter()
+ .isUsedEqualTo(true)
+ .lTagHashProperty()
+ .findAll();
final pairs = await FiroCacheCoordinator.getUsedCoinTxidsFor(
tags: tags,
@@ -1272,8 +1236,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
Logging.instance.i("Refreshing spark names for $walletId ${info.name}");
final db = Drift.get(walletId);
- final myNameStrings =
- await db.managers.sparkNames.map((e) => e.name).get();
+ final myNameStrings = await db.managers.sparkNames
+ .map((e) => e.name)
+ .get();
final names = await electrumXClient.getSparkNames();
// start update shared cache of all names
@@ -1304,9 +1269,11 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final root = await getRootHDNode();
final String derivationPath;
if (cryptoCurrency.network.isTestNet) {
- derivationPath = "$kSparkBaseDerivationPathTestnet$kDefaultSparkIndex";
+ derivationPath =
+ "${libSpark.sparkBaseDerivationPathTestnet}$kDefaultSparkIndex";
} else {
- derivationPath = "$kSparkBaseDerivationPath$kDefaultSparkIndex";
+ derivationPath =
+ "${libSpark.sparkBaseDerivationPath}$kDefaultSparkIndex";
}
final keys = root.derivePath(derivationPath);
@@ -1317,10 +1284,10 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
while (diversifier < maxDiversifier) {
// change address check
- if (diversifier == kSparkChange) {
+ if (diversifier == libSpark.sparkChange) {
diversifier++;
}
- final addressString = await LibSpark.getAddress(
+ final addressString = await libSpark.getAddress(
privateKey: keys.privateKey.data,
index: kDefaultSparkIndex,
diversifier: diversifier,
@@ -1415,10 +1382,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
// setup some vars
int nChangePosInOut = -1;
final int nChangePosRequest = nChangePosInOut;
- List<MutableSparkRecipient> outputs_ =
- outputs
- .map((e) => MutableSparkRecipient(e.address, e.value, e.memo))
- .toList(); // deep copy
+ List<MutableSparkRecipient> outputs_ = outputs
+ .map((e) => MutableSparkRecipient(e.address, e.value, e.memo))
+ .toList(); // deep copy
final feesObject = await fees;
final currentHeight = await chainHeight;
final random = Random.secure();
@@ -1427,10 +1393,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
valueAndUTXOs.shuffle(random);
while (valueAndUTXOs.isNotEmpty) {
- final lockTime =
- random.nextInt(10) == 0
- ? max(0, currentHeight - random.nextInt(100))
- : currentHeight;
+ final lockTime = random.nextInt(10) == 0
+ ? max(0, currentHeight - random.nextInt(100))
+ : currentHeight;
const txVersion = 1;
final List<StandardInput> vin = [];
final List<(dynamic, int, String?)> vout = [];
@@ -1477,10 +1442,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
setCoins.clear();
// deep copy
- final remainingOutputs =
- outputs_
- .map((e) => MutableSparkRecipient(e.address, e.value, e.memo))
- .toList();
+ final remainingOutputs = outputs_
+ .map((e) => MutableSparkRecipient(e.address, e.value, e.memo))
+ .toList();
final List<MutableSparkRecipient> singleTxOutputs = [];
if (autoMintAll) {
@@ -1540,17 +1504,13 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
}
// Generate dummy mint coins to save time
- final dummyRecipients = LibSpark.createSparkMintRecipients(
- outputs:
- singleTxOutputs
- .map(
- (e) => (
- sparkAddress: e.address,
- value: e.value.toInt(),
- memo: "",
- ),
- )
- .toList(),
+ final dummyRecipients = libSpark.createSparkMintRecipients(
+ outputs: singleTxOutputs
+ .map(
+ (e) =>
+ (sparkAddress: e.address, value: e.value.toInt(), memo: ""),
+ )
+ .toList(),
serialContext: Uint8List(0),
generate: false,
);
@@ -1623,40 +1583,36 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
switch (sd.derivePathType) {
case DerivePathType.bip44:
- data =
- btc
- .P2PKH(
- data: btc.PaymentData(pubkey: pubKey),
- network: _bitcoinDartNetwork,
- )
- .data;
+ data = btc
+ .P2PKH(
+ data: btc.PaymentData(pubkey: pubKey),
+ network: _bitcoinDartNetwork,
+ )
+ .data;
break;
case DerivePathType.bip49:
- final p2wpkh =
- btc
- .P2WPKH(
- data: btc.PaymentData(pubkey: pubKey),
- network: _bitcoinDartNetwork,
- )
- .data;
- data =
- btc
- .P2SH(
- data: btc.PaymentData(redeem: p2wpkh),
- network: _bitcoinDartNetwork,
- )
- .data;
+ final p2wpkh = btc
+ .P2WPKH(
+ data: btc.PaymentData(pubkey: pubKey),
+ network: _bitcoinDartNetwork,
+ )
+ .data;
+ data = btc
+ .P2SH(
+ data: btc.PaymentData(redeem: p2wpkh),
+ network: _bitcoinDartNetwork,
+ )
+ .data;
break;
case DerivePathType.bip84:
- data =
- btc
- .P2WPKH(
- data: btc.PaymentData(pubkey: pubKey),
- network: _bitcoinDartNetwork,
- )
- .data;
+ data = btc
+ .P2WPKH(
+ data: btc.PaymentData(pubkey: pubKey),
+ network: _bitcoinDartNetwork,
+ )
+ .data;
break;
case DerivePathType.bip86:
@@ -1715,20 +1671,19 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
}
// Generate real mint coins
- final serialContext = LibSpark.serializeMintContext(
+ final serialContext = libSpark.serializeMintContext(
inputs: setCoins.map((e) => (e.utxo.txid, e.utxo.vout)).toList(),
);
- final recipients = LibSpark.createSparkMintRecipients(
- outputs:
- singleTxOutputs
- .map(
- (e) => (
- sparkAddress: e.address,
- memo: e.memo,
- value: e.value.toInt(),
- ),
- )
- .toList(),
+ final recipients = libSpark.createSparkMintRecipients(
+ outputs: singleTxOutputs
+ .map(
+ (e) => (
+ sparkAddress: e.address,
+ memo: e.memo,
+ value: e.value.toInt(),
+ ),
+ )
+ .toList(),
serialContext: serialContext,
generate: true,
);
@@ -1753,10 +1708,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
}
// deep copy
- outputs_ =
- remainingOutputs
- .map((e) => MutableSparkRecipient(e.address, e.value, e.memo))
- .toList();
+ outputs_ = remainingOutputs
+ .map((e) => MutableSparkRecipient(e.address, e.value, e.memo))
+ .toList();
break; // Done, enough fee included.
}
@@ -1784,40 +1738,36 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
switch (input.derivePathType) {
case DerivePathType.bip44:
- data =
- btc
- .P2PKH(
- data: btc.PaymentData(pubkey: pubKey),
- network: _bitcoinDartNetwork,
- )
- .data;
+ data = btc
+ .P2PKH(
+ data: btc.PaymentData(pubkey: pubKey),
+ network: _bitcoinDartNetwork,
+ )
+ .data;
break;
case DerivePathType.bip49:
- final p2wpkh =
- btc
- .P2WPKH(
- data: btc.PaymentData(pubkey: pubKey),
- network: _bitcoinDartNetwork,
- )
- .data;
- data =
- btc
- .P2SH(
- data: btc.PaymentData(redeem: p2wpkh),
- network: _bitcoinDartNetwork,
- )
- .data;
+ final p2wpkh = btc
+ .P2WPKH(
+ data: btc.PaymentData(pubkey: pubKey),
+ network: _bitcoinDartNetwork,
+ )
+ .data;
+ data = btc
+ .P2SH(
+ data: btc.PaymentData(redeem: p2wpkh),
+ network: _bitcoinDartNetwork,
+ )
+ .data;
break;
case DerivePathType.bip84:
- data =
- btc
- .P2WPKH(
- data: btc.PaymentData(pubkey: pubKey),
- network: _bitcoinDartNetwork,
- )
- .data;
+ data = btc
+ .P2WPKH(
+ data: btc.PaymentData(pubkey: pubKey),
+ network: _bitcoinDartNetwork,
+ )
+ .data;
break;
case DerivePathType.bip86:
@@ -1862,8 +1812,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
tempOutputs.add(
OutputV2.isarCantDoRequiredInDefaultConstructor(
- scriptPubKeyHex:
- addressOrScript is Uint8List ? addressOrScript.toHex : "000000",
+ scriptPubKeyHex: addressOrScript is Uint8List
+ ? addressOrScript.toHex
+ : "000000",
valueStringSats: value.toString(),
addresses: [
if (addressOrScript is String) addressOrScript.toString(),
@@ -1914,24 +1865,22 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
assert(outputs.length == 1);
final data = TxData(
- sparkRecipients:
- vout
- .where((e) => e.$1 is Uint8List) // ignore change
- .map(
- (e) => (
- address:
- outputs
- .first
- .address, // for display purposes on confirm tx screen. See todos above
- memo: "",
- amount: Amount(
- rawValue: BigInt.from(e.$2),
- fractionDigits: cryptoCurrency.fractionDigits,
- ),
- isChange: false, // ok?
- ),
- )
- .toList(),
+ sparkRecipients: vout
+ .where((e) => e.$1 is Uint8List) // ignore change
+ .map(
+ (e) => (
+ address: outputs
+ .first
+ .address, // for display purposes on confirm tx screen. See todos above
+ memo: "",
+ amount: Amount(
+ rawValue: BigInt.from(e.$2),
+ fractionDigits: cryptoCurrency.fractionDigits,
+ ),
+ isChange: false, // ok?
+ ),
+ )
+ .toList(),
vSize: builtTx.virtualSize(),
txid: builtTx.getId(),
raw: builtTx.toHex(),
@@ -1950,8 +1899,8 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
outputs: List.unmodifiable(tempOutputs),
type:
tempOutputs.map((e) => e.walletOwns).fold(true, (p, e) => p &= e)
- ? TransactionType.sentToSelf
- : TransactionType.outgoing,
+ ? TransactionType.sentToSelf
+ : TransactionType.outgoing,
subType: TransactionSubType.sparkMint,
otherData: null,
height: null,
@@ -2018,25 +1967,23 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
const subtractFeeFromAmount = true; // must be true for mint all
final currentHeight = await chainHeight;
- final spendableUtxos =
- await mainDB.isar.utxos
- .where()
- .walletIdEqualTo(walletId)
- .filter()
- .isBlockedEqualTo(false)
- .and()
- .group((q) => q.usedEqualTo(false).or().usedIsNull())
- .and()
- .valueGreaterThan(0)
- .findAll();
+ final spendableUtxos = await mainDB.isar.utxos
+ .where()
+ .walletIdEqualTo(walletId)
+ .filter()
+ .isBlockedEqualTo(false)
+ .and()
+ .group((q) => q.usedEqualTo(false).or().usedIsNull())
+ .and()
+ .valueGreaterThan(0)
+ .findAll();
spendableUtxos.removeWhere(
- (e) =>
- !e.isConfirmed(
- currentHeight,
- cryptoCurrency.minConfirms,
- cryptoCurrency.minCoinbaseConfirms,
- ),
+ (e) => !e.isConfirmed(
+ currentHeight,
+ cryptoCurrency.minConfirms,
+ cryptoCurrency.minCoinbaseConfirms,
+ ),
);
if (spendableUtxos.isEmpty) {
@@ -2077,12 +2024,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
if (txData.sparkRecipients?.isNotEmpty != true) {
throw Exception("Missing spark recipients.");
}
- final recipients =
- txData.sparkRecipients!
- .map(
- (e) => MutableSparkRecipient(e.address, e.amount.raw, e.memo),
- )
- .toList();
+ final recipients = txData.sparkRecipients!
+ .map((e) => MutableSparkRecipient(e.address, e.amount.raw, e.memo))
+ .toList();
final total = recipients
.map((e) => e.value)
@@ -2094,16 +2038,17 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
throw Exception("Attempted send of zero amount");
}
- final utxos =
- txData.utxos?.whereType<StandardInput>().map((e) => e.utxo).toList();
+ final utxos = txData.utxos
+ ?.whereType<StandardInput>()
+ .map((e) => e.utxo)
+ .toList();
final bool coinControl = utxos != null;
- final utxosTotal =
- coinControl
- ? utxos
- .map((e) => e.value)
- .fold(BigInt.zero, (p, e) => p + BigInt.from(e))
- : null;
+ final utxosTotal = coinControl
+ ? utxos
+ .map((e) => e.value)
+ .fold(BigInt.zero, (p, e) => p + BigInt.from(e))
+ : null;
if (coinControl && utxosTotal! < total) {
throw Exception("Insufficient selected UTXOs!");
@@ -2128,18 +2073,17 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final canCPFP = this is CpfpInterface && coinControl;
- final spendableUtxos =
- availableOutputs
- .where(
- (e) =>
- canCPFP ||
- e.isConfirmed(
- currentHeight,
- cryptoCurrency.minConfirms,
- cryptoCurrency.minCoinbaseConfirms,
- ),
- )
- .toList();
+ final spendableUtxos = availableOutputs
+ .where(
+ (e) =>
+ canCPFP ||
+ e.isConfirmed(
+ currentHeight,
+ cryptoCurrency.minConfirms,
+ cryptoCurrency.minCoinbaseConfirms,
+ ),
+ )
+ .toList();
if (spendableUtxos.isEmpty) {
throw Exception("No available UTXOs found to anonymize");
@@ -2197,21 +2141,21 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
}
}
- if (years < 1 || years > kMaxNameRegistrationLengthYears) {
+ if (years < 1 || years > libSpark.maxNameRegistrationLengthYears) {
throw Exception("Invalid spark name registration period years: $years");
}
- if (name.isEmpty || name.length > kMaxNameLength) {
+ if (name.isEmpty || name.length > libSpark.maxNameLength) {
throw Exception("Invalid spark name length: ${name.length}");
}
- if (!RegExp(kNameRegexString).hasMatch(name)) {
+ if (!RegExp(libSpark.nameRegexString).hasMatch(name)) {
throw Exception("Invalid symbols found in spark name: $name");
}
if (additionalInfo.toUint8ListFromUtf8.length >
- kMaxAdditionalInfoLengthBytes) {
+ libSpark.maxAdditionalInfoLengthBytes) {
throw Exception(
- "Additional info exceeds $kMaxAdditionalInfoLengthBytes bytes.",
+ "Additional info exceeds ${libSpark.maxAdditionalInfoLengthBytes} bytes.",
);
}
@@ -2233,11 +2177,11 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final String destinationAddress;
switch (cryptoCurrency.network) {
case CryptoCurrencyNetwork.main:
- destinationAddress = kStage3DevelopmentFundAddressMainNet;
+ destinationAddress = libSpark.stage3DevelopmentFundAddressMainNet;
break;
case CryptoCurrencyNetwork.test:
- destinationAddress = kStage3DevelopmentFundAddressTestNet;
+ destinationAddress = libSpark.stage3DevelopmentFundAddressTestNet;
break;
default:
@@ -2253,7 +2197,9 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
TxRecipient(
address: destinationAddress,
amount: Amount.fromDecimal(
- Decimal.fromInt(kStandardSparkNamesFee[name.length] * years),
+ Decimal.fromInt(
+ libSpark.standardSparkNamesFee[name.length] * years,
+ ),
fractionDigits: cryptoCurrency.fractionDigits,
),
isChange: false,
@@ -2347,7 +2293,7 @@ _createSparkSend(
})
args,
) async {
- final spend = LibSpark.createSparkSendTransaction(
+ final spend = libSpark.createSparkSendTransaction(
privateKeyHex: args.privateKeyHex,
index: args.index,
recipients: args.recipients,
@@ -2387,7 +2333,7 @@ Future<List<SparkCoin>> _identifyCoins(
final txHash = data[1].toHexReversedFromBase64;
final contextB64 = data[2];
- final coin = LibSpark.identifyAndRecoverCoin(
+ final coin = libSpark.identifyAndRecoverCoin(
serializedCoinB64,
privateKeyHex: privateKeyHex,
index: kDefaultSparkIndex,
@@ -2460,13 +2406,12 @@ class MutableSparkRecipient {
}
}
-typedef SerializedCoinData =
- ({
- int groupId,
- int height,
- String serializedCoin,
- String serializedCoinContext,
- });
+typedef SerializedCoinData = ({
+ int groupId,
+ int height,
+ String serializedCoin,
+ String serializedCoinContext,
+});
Future<int> _asyncSparkFeesWrapper({
required String privateKeyHex,
@@ -2503,7 +2448,7 @@ int _estSparkFeeComputeFunc(
})
args,
) {
- final est = LibSpark.estimateSparkFee(
+ final est = libSpark.estimateSparkFee(
privateKeyHex: args.privateKeyHex,
index: args.index,
sendAmount: args.sendAmount,
diff --git a/lib/wl_gen/interfaces/lib_spark_interface.dart b/lib/wl_gen/interfaces/lib_spark_interface.dart
new file mode 100644
index 0000000..5f24277
--- /dev/null
+++ b/lib/wl_gen/interfaces/lib_spark_interface.dart
@@ -0,0 +1,252 @@
+import 'dart:typed_data';
+
+import 'package:logger/logger.dart';
+
+export '../generated/lib_spark_interface_impl.dart';
+
+abstract class LibSparkInterface {
+ const LibSparkInterface();
+
+ String get sparkBaseDerivationPath;
+ String get sparkBaseDerivationPathTestnet;
+ int get sparkChange;
+ int get maxNameRegistrationLengthYears;
+ int get maxNameLength;
+ int get maxAdditionalInfoLengthBytes;
+ String get nameRegexString;
+ String get stage3DevelopmentFundAddressMainNet;
+ String get stage3DevelopmentFundAddressTestNet;
+ List<int> get standardSparkNamesFee;
+
+ void initSparkLogging(Level level);
+
+ String hashTag(String x, String y);
+
+ bool validateAddress({required String address, required bool isTestNet});
+
+ Future<String> getAddress({
+ required Uint8List privateKey,
+ required int index,
+ required int diversifier,
+ bool isTestNet = false,
+ });
+
+ ({Uint8List script, int size}) createSparkNameScript({
+ required int sparkNameValidityBlocks,
+ required String name,
+ required String additionalInfo,
+ required String scalarHex,
+ required String privateKeyHex,
+ required int spendKeyIndex,
+ required int diversifier,
+ required bool isTestNet,
+ required int hashFailSafe,
+ required bool ignoreProof,
+ });
+
+ List<({Uint8List scriptPubKey, int amount, bool subtractFeeFromAmount})>
+ createSparkMintRecipients({
+ required List<({String sparkAddress, int value, String memo})> outputs,
+ required Uint8List serialContext,
+ bool generate = false,
+ });
+
+ Uint8List serializeMintContext({required List<(String, int)> inputs});
+
+ WrappedLibSparkCoin? identifyAndRecoverCoin(
+ final String serializedCoin, {
+ required final String privateKeyHex,
+ required final int index,
+ required final Uint8List context,
+ final bool isTestNet = false,
+ });
+
+ ({
+ Uint8List serializedSpendPayload,
+ List<Uint8List> outputScripts,
+ int fee,
+ List<
+ ({
+ String serializedCoin,
+ String serializedCoinContext,
+ int groupId,
+ int height,
+ })
+ >
+ usedCoins,
+ })
+ createSparkSendTransaction({
+ required String privateKeyHex,
+ int index = 1,
+ required List<({String address, int amount, bool subtractFeeFromAmount})>
+ recipients,
+ required List<
+ ({
+ String sparkAddress,
+ int amount,
+ bool subtractFeeFromAmount,
+ String memo,
+ })
+ >
+ privateRecipients,
+ required List<
+ ({
+ String serializedCoin,
+ String serializedCoinContext,
+ int groupId,
+ int height,
+ })
+ >
+ serializedCoins,
+ required List<
+ ({
+ int setId,
+ String setHash,
+ List<({String serializedCoin, String txHash})> set,
+ })
+ >
+ allAnonymitySets,
+ required List<({int setId, Uint8List blockHash})> idAndBlockHashes,
+ required Uint8List txHash,
+ required int additionalTxSize,
+ });
+
+ int estimateSparkFee({
+ required String privateKeyHex,
+ int index = 1,
+ required int sendAmount,
+ required bool subtractFeeFromAmount,
+ required List<
+ ({
+ String serializedCoin,
+ String serializedCoinContext,
+ int groupId,
+ int height,
+ })
+ >
+ serializedCoins,
+ required int privateRecipientsCount,
+ required int utxoNum,
+ required int additionalTxSize,
+ });
+}
+
+// stupid
+enum WrappedLibSparkCoinType {
+ mint(0),
+ spend(1);
+
+ const WrappedLibSparkCoinType(this.value);
+ final int value;
+}
+
+// stupid
+final class WrappedLibSparkCoin {
+ final WrappedLibSparkCoinType type;
+
+ final int? id;
+ final int? height;
+
+ final bool? isUsed;
+
+ final String? nonceHex;
+
+ final String? address;
+
+ final BigInt? value;
+
+ final String? memo;
+
+ final Uint8List? txHash;
+
+ final Uint8List? serialContext;
+
+ final BigInt? diversifier;
+ final Uint8List? encryptedDiversifier;
+
+ final Uint8List? serial;
+ final Uint8List? tag;
+
+ final String? lTagHash;
+
+ final String? serializedCoin;
+
+ WrappedLibSparkCoin({
+ required this.type,
+ this.id,
+ this.height,
+ this.isUsed,
+ this.nonceHex,
+ this.address,
+ this.value,
+ this.memo,
+ this.txHash,
+ this.serialContext,
+ this.diversifier,
+ this.encryptedDiversifier,
+ this.serial,
+ this.tag,
+ this.lTagHash,
+ this.serializedCoin,
+ });
+
+ WrappedLibSparkCoin copyWith({
+ WrappedLibSparkCoinType? type,
+ int? id,
+ int? height,
+ bool? isUsed,
+ String? nonceHex,
+ String? address,
+ BigInt? value,
+ String? memo,
+ Uint8List? txHash,
+ Uint8List? serialContext,
+ BigInt? diversifier,
+ Uint8List? encryptedDiversifier,
+ Uint8List? serial,
+ Uint8List? tag,
+ String? lTagHash,
+ String? serializedCoin,
+ }) {
+ return WrappedLibSparkCoin(
+ type: type ?? this.type,
+ id: id ?? this.id,
+ height: height ?? this.height,
+ isUsed: isUsed ?? this.isUsed,
+ nonceHex: nonceHex ?? this.nonceHex,
+ address: address ?? this.address,
+ value: value ?? this.value,
+ memo: memo ?? this.memo,
+ txHash: txHash ?? this.txHash,
+ serialContext: serialContext ?? this.serialContext,
+ diversifier: diversifier ?? this.diversifier,
+ encryptedDiversifier: encryptedDiversifier ?? this.encryptedDiversifier,
+ serial: serial ?? this.serial,
+ tag: tag ?? this.tag,
+ lTagHash: lTagHash ?? this.lTagHash,
+ serializedCoin: serializedCoin ?? this.serializedCoin,
+ );
+ }
+
+ @override
+ String toString() {
+ return 'WrappedLibSparkCoin('
+ ', type: $type'
+ ', id: $id'
+ ', height: $height'
+ ', isUsed: $isUsed'
+ ', k: $nonceHex'
+ ', address: $address'
+ ', value: $value'
+ ', memo: $memo'
+ ', txHash: $txHash'
+ ', serialContext: $serialContext'
+ ', diversifier: $diversifier'
+ ', encryptedDiversifier: $encryptedDiversifier'
+ ', serial: $serial'
+ ', tag: $tag'
+ ', lTagHash: $lTagHash'
+ ', serializedCoin: $serializedCoin'
+ ')';
+ }
+}
diff --git a/pubspec.lock b/pubspec.lock
index a1b9af3..6318fed 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -178,10 +178,10 @@ packages:
dependency: transitive
description:
name: build_cli_annotations
- sha256: b59d2769769efd6c9ff6d4c4cede0be115a566afc591705c2040b707534b1172
+ sha256: e563c2e01de8974566a1998410d3f6f03521788160a02503b0b1f1a46c7b3d95
url: "https://pub.dev"
source: hosted
- version: "2.1.0"
+ version: "2.1.1"
build_config:
dependency: transitive
description:
diff --git a/tool/wl_templates/FIRO_lib_spark_interface_impl.template.dart b/tool/wl_templates/FIRO_lib_spark_interface_impl.template.dart
new file mode 100644
index 0000000..2403a81
--- /dev/null
+++ b/tool/wl_templates/FIRO_lib_spark_interface_impl.template.dart
@@ -0,0 +1,290 @@
+//ON
+import 'dart:typed_data';
+
+import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
+import 'package:logger/logger.dart';
+
+import '../../utilities/logger.dart';
+//END_ON
+import '../interfaces/lib_spark_interface.dart';
+
+LibSparkInterface get libSpark => _getInterface();
+
+//OFF
+LibSparkInterface _getInterface() => throw Exception("FIRO not enabled!");
+
+List<String> hashTags({required List<String> base64Tags}) =>
+ throw Exception("FIRO not enabled!");
+
+//END_OFF
+//ON
+LibSparkInterface _getInterface() => _LibSparkInterfaceImpl();
+
+List<String> hashTags({required List<String> base64Tags}) =>
+ LibSpark.hashTags(base64Tags: base64Tags);
+
+class _LibSparkInterfaceImpl extends LibSparkInterface {
+ @override
+ String get sparkBaseDerivationPath => kSparkBaseDerivationPath;
+
+ @override
+ String get sparkBaseDerivationPathTestnet => kSparkBaseDerivationPathTestnet;
+
+ @override
+ int get sparkChange => kSparkChange;
+
+ @override
+ int get maxAdditionalInfoLengthBytes => kMaxAdditionalInfoLengthBytes;
+
+ @override
+ int get maxNameLength => kMaxNameLength;
+
+ @override
+ int get maxNameRegistrationLengthYears => kMaxNameRegistrationLengthYears;
+
+ @override
+ String get nameRegexString => kNameRegexString;
+
+ @override
+ String get stage3DevelopmentFundAddressMainNet =>
+ kStage3DevelopmentFundAddressMainNet;
+
+ @override
+ String get stage3DevelopmentFundAddressTestNet =>
+ kStage3DevelopmentFundAddressTestNet;
+
+ @override
+ List<int> get standardSparkNamesFee =>
+ List.unmodifiable(kStandardSparkNamesFee);
+
+ @override
+ void initSparkLogging(Level level) {
+ final levels = Level.values.where((e) => e >= level).map((e) => e.name);
+ Log.levels.addAll(
+ LoggingLevel.values.where((e) => levels.contains(e.name)),
+ );
+ Log.onLog = (level, value, {error, stackTrace, required time}) {
+ Logging.instance.log(
+ level.getLoggerLevel(),
+ value,
+ error: error,
+ stackTrace: stackTrace,
+ time: time,
+ );
+ };
+ }
+
+ @override
+ String hashTag(String x, String y) => LibSpark.hashTag(x, y);
+
+ @override
+ bool validateAddress({required String address, required bool isTestNet}) =>
+ LibSpark.validateAddress(address: address, isTestNet: isTestNet);
+
+ @override
+ Future<String> getAddress({
+ required Uint8List privateKey,
+ required int index,
+ required int diversifier,
+ bool isTestNet = false,
+ }) => LibSpark.getAddress(
+ privateKey: privateKey,
+ index: index,
+ diversifier: diversifier,
+ isTestNet: isTestNet,
+ );
+
+ @override
+ ({Uint8List script, int size}) createSparkNameScript({
+ required int sparkNameValidityBlocks,
+ required String name,
+ required String additionalInfo,
+ required String scalarHex,
+ required String privateKeyHex,
+ required int spendKeyIndex,
+ required int diversifier,
+ required bool isTestNet,
+ required int hashFailSafe,
+ required bool ignoreProof,
+ }) => LibSpark.createSparkNameScript(
+ sparkNameValidityBlocks: sparkNameValidityBlocks,
+ name: name,
+ additionalInfo: additionalInfo,
+ scalarHex: scalarHex,
+ privateKeyHex: privateKeyHex,
+ spendKeyIndex: spendKeyIndex,
+ diversifier: diversifier,
+ isTestNet: isTestNet,
+ hashFailSafe: hashFailSafe,
+ ignoreProof: ignoreProof,
+ );
+
+ @override
+ List<({int amount, Uint8List scriptPubKey, bool subtractFeeFromAmount})>
+ createSparkMintRecipients({
+ required List<({String memo, String sparkAddress, int value})> outputs,
+ required Uint8List serialContext,
+ bool generate = false,
+ }) => LibSpark.createSparkMintRecipients(
+ outputs: outputs,
+ serialContext: serialContext,
+ generate: generate,
+ );
+
+ @override
+ Uint8List serializeMintContext({required List<(String, int)> inputs}) =>
+ LibSpark.serializeMintContext(inputs: inputs);
+
+ @override
+ WrappedLibSparkCoin? identifyAndRecoverCoin(
+ String serializedCoin, {
+ required String privateKeyHex,
+ required int index,
+ required Uint8List context,
+ bool isTestNet = false,
+ }) {
+ final coin = LibSpark.identifyAndRecoverCoin(
+ serializedCoin,
+ privateKeyHex: privateKeyHex,
+ index: index,
+ context: context,
+ isTestNet: isTestNet,
+ );
+
+ if (coin == null) return null;
+
+ return WrappedLibSparkCoin(
+ type: WrappedLibSparkCoinType.values.firstWhere(
+ (e) => e.value == coin.type.value,
+ ),
+
+ id: coin.id,
+ height: coin.height,
+ isUsed: coin.isUsed,
+ nonceHex: coin.nonceHex,
+ address: coin.address,
+ value: coin.value,
+ serial: coin.serial,
+ memo: coin.memo,
+ txHash: coin.txHash,
+ serialContext: coin.serialContext,
+ diversifier: coin.diversifier,
+ encryptedDiversifier: coin.encryptedDiversifier,
+ tag: coin.tag,
+ lTagHash: coin.lTagHash,
+ serializedCoin: coin.serializedCoin,
+ );
+ }
+
+ @override
+ ({
+ int fee,
+ List<Uint8List> outputScripts,
+ Uint8List serializedSpendPayload,
+ List<
+ ({
+ int groupId,
+ int height,
+ String serializedCoin,
+ String serializedCoinContext,
+ })
+ >
+ usedCoins,
+ })
+ createSparkSendTransaction({
+ required String privateKeyHex,
+ int index = 1,
+ required List<({String address, int amount, bool subtractFeeFromAmount})>
+ recipients,
+ required List<
+ ({
+ int amount,
+ String memo,
+ String sparkAddress,
+ bool subtractFeeFromAmount,
+ })
+ >
+ privateRecipients,
+ required List<
+ ({
+ int groupId,
+ int height,
+ String serializedCoin,
+ String serializedCoinContext,
+ })
+ >
+ serializedCoins,
+ required List<
+ ({
+ List<({String serializedCoin, String txHash})> set,
+ String setHash,
+ int setId,
+ })
+ >
+ allAnonymitySets,
+ required List<({Uint8List blockHash, int setId})> idAndBlockHashes,
+ required Uint8List txHash,
+ required int additionalTxSize,
+ }) => LibSpark.createSparkSendTransaction(
+ index: index,
+ privateKeyHex: privateKeyHex,
+ recipients: recipients,
+ privateRecipients: privateRecipients,
+ serializedCoins: serializedCoins,
+ allAnonymitySets: allAnonymitySets,
+ idAndBlockHashes: idAndBlockHashes,
+ txHash: txHash,
+ additionalTxSize: additionalTxSize,
+ );
+
+ @override
+ int estimateSparkFee({
+ required String privateKeyHex,
+ int index = 1,
+ required int sendAmount,
+ required bool subtractFeeFromAmount,
+ required List<
+ ({
+ int groupId,
+ int height,
+ String serializedCoin,
+ String serializedCoinContext,
+ })
+ >
+ serializedCoins,
+ required int privateRecipientsCount,
+ required int utxoNum,
+ required int additionalTxSize,
+ }) => LibSpark.estimateSparkFee(
+ privateKeyHex: privateKeyHex,
+ sendAmount: sendAmount,
+ subtractFeeFromAmount: subtractFeeFromAmount,
+ serializedCoins: serializedCoins,
+ privateRecipientsCount: privateRecipientsCount,
+ utxoNum: utxoNum,
+ additionalTxSize: additionalTxSize,
+ index: index,
+ );
+}
+
+// convenience conversion for spark
+extension _LoggingLevelExt on LoggingLevel {
+ Level getLoggerLevel() {
+ switch (this) {
+ case LoggingLevel.info:
+ return Level.info;
+ case LoggingLevel.warning:
+ return Level.warning;
+ case LoggingLevel.error:
+ return Level.error;
+ case LoggingLevel.fatal:
+ return Level.fatal;
+ case LoggingLevel.debug:
+ return Level.debug;
+ case LoggingLevel.trace:
+ return Level.trace;
+ }
+ }
+}
+
+//END_ON
Why this scored 27/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.