Fix Spark Name registration fee sizing
What changed, and why it matters
This commit fixes how the Stack Wallet calculates transaction fees for a feature called Spark Name registration. Previously, the wallet did not correctly account for the extra data added to the transaction when registering a Spark Name, and it also treated name-registration payments like ordinary 'send all' transactions. The fix ensures the fee is sized properly and that the miner fee is never subtracted from the amount being sent for a name registration. In practical terms, before the fix a user registering a Spark Name might have paid an unexpectedly low fee, causing the transaction to be rejected or delayed by the network, or the wallet might have tried to subtract the fee from the registration amount, potentially sending less than intended.
Review the Spark Name registration flow end-to-end to confirm the fee script size delta is applied consistently across all code paths that build Spark Name transactions, and consider adding integration tests that verify a name-registration transaction is accepted by the network with the computed fee.
Security signals we found
Incorrect transaction fee sizing for Spark Name registration outputs
Potential fee subtraction from the registration output amount on send-all transactions
Addition of unit tests to verify fee script size delta and fee-subtraction behavior
Evidence from the diff
The patch changes lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart to correctly size the Spark Name registration fee. It exposes _sparkNameFeeScript and a new shouldSubtractSparkFeeFromAmount helper as @visibleForTesting. The key logic changes are: (1) isSendAll is now computed via shouldSubtractSparkFeeFromAmount, which returns false when the transaction is a Spark Name registration, preventing the miner fee from being subtracted from the output amount; (2) the loop building transaction outputs now tracks sparkNameFeeScriptSizeDelta, the extra bytes added by wrapping the output script with the Spark Name fee script; (3) this delta is added to additionalTxSize passed to the Spark spend operation, so the transaction fee calculation includes the actual size of the name-registration output. A new unit test verifies the 155-byte delta and the send-all behavior.
Changed components
lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dartSpark Name registration transaction constructionSpark transaction fee estimationInspect captured patch +52 / −4
diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart
index eaf82f4..bf3e627 100644
--- a/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart
+++ b/lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart
@@ -64,7 +64,8 @@ String _hashTag(String tag) {
return hash;
}
-Uint8List _sparkNameFeeScript({
+@visibleForTesting
+Uint8List sparkNameFeeScript({
required Uint8List baseScript,
required String name,
required String sparkAddress,
@@ -79,6 +80,12 @@ Uint8List _sparkNameFeeScript({
]),
]);
+@visibleForTesting
+bool shouldSubtractSparkFeeFromAmount({
+ required bool isSparkNameRegistration,
+ required bool spendsAll,
+}) => !isSparkNameRegistration && spendsAll;
+
void initSparkLogging(Level level) => libSpark.initSparkLogging(level);
abstract class _SparkIsolate {
@@ -586,7 +593,10 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
throw Exception("Insufficient Spark balance");
}
- final bool isSendAll = available == txAmount;
+ final bool isSendAll = shouldSubtractSparkFeeFromAmount(
+ isSparkNameRegistration: txData.sparkNameInfo != null,
+ spendsAll: available == txAmount,
+ );
// prepare coin data for ffi
final serializedCoins = coins
@@ -711,6 +721,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
final List<InputV2> tempInputs = [];
final List<OutputV2> tempOutputs = [];
+ var sparkNameFeeScriptSizeDelta = 0;
for (int i = 0; i < (txData.recipients?.length ?? 0); i++) {
if (txData.recipients![i].amount.raw == BigInt.zero) {
continue;
@@ -731,11 +742,13 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
_bitcoinDartNetwork,
);
if (txData.sparkNameInfo != null) {
- scriptPubKey = _sparkNameFeeScript(
+ final baseScript = scriptPubKey;
+ scriptPubKey = sparkNameFeeScript(
baseScript: scriptPubKey,
name: txData.sparkNameInfo!.name,
sparkAddress: txData.sparkNameInfo!.sparkAddress.value,
);
+ sparkNameFeeScriptSizeDelta += scriptPubKey.length - baseScript.length;
}
txb.addOutput(
scriptPubKey,
@@ -841,7 +854,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
txHash: extractedTx.getHash(),
additionalTxSize: txData.sparkNameInfo == null
? 0
- : noProofNameTxData!.size,
+ : noProofNameTxData!.size + sparkNameFeeScriptSizeDelta,
));
for (final outputScript in spend.outputScripts) {
diff --git a/test/wallets/spark_name_fee_test.dart b/test/wallets/spark_name_fee_test.dart
new file mode 100644
index 0000000..442dd3d
--- /dev/null
+++ b/test/wallets/spark_name_fee_test.dart
@@ -0,0 +1,35 @@
+import 'dart:typed_data';
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
+
+void main() {
+ test('Spark Name fee size includes the tagged output bytes', () {
+ final baseScript = Uint8List(25);
+ final feeScript = sparkNameFeeScript(
+ baseScript: baseScript,
+ name: 'alice',
+ sparkAddress: List.filled(144, 'a').join(),
+ );
+
+ expect(feeScript.length - baseScript.length, 155);
+ expect(feeScript.length, 180);
+ });
+
+ test('Spark Name payments never have the miner fee subtracted', () {
+ expect(
+ shouldSubtractSparkFeeFromAmount(
+ isSparkNameRegistration: true,
+ spendsAll: true,
+ ),
+ isFalse,
+ );
+ expect(
+ shouldSubtractSparkFeeFromAmount(
+ isSparkNameRegistration: false,
+ spendsAll: true,
+ ),
+ isTrue,
+ );
+ });
+}
Why this scored 44/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.