Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'dart:isolate';
import 'dart:math';

import 'package:bitcoindart/bitcoindart.dart' as btc;
import 'package:bitcoindart/src/utils/script.dart' as bscript;
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
import 'package:decimal/decimal.dart';
import 'package:flutter/foundation.dart';
Expand Down Expand Up @@ -50,6 +51,8 @@ const SPARK_OUT_LIMIT_PER_TX = 16;
const OP_SPARKMINT = 0xd1;
const OP_SPARKSMINT = 0xd2;
const OP_SPARKSPEND = 0xd3;
const OP_SPARKNAMEID = 0xe1;
const OP_DROP = 0x75;

/// top level function for use with [compute]
String _hashTag(String tag) {
Expand All @@ -61,6 +64,28 @@ String _hashTag(String tag) {
return hash;
}

@visibleForTesting
Uint8List sparkNameFeeScript({
required Uint8List baseScript,
required String name,
required String sparkAddress,
}) => Uint8List.fromList([
...baseScript,
...bscript.compile([
OP_SPARKNAMEID,
Uint8List.fromList(utf8.encode(name)),
OP_DROP,
Uint8List.fromList(utf8.encode(sparkAddress)),
OP_DROP,
]),
]);

@visibleForTesting
bool shouldSubtractSparkFeeFromAmount({
required bool isSparkNameRegistration,
required bool spendsAll,
}) => !isSparkNameRegistration && spendsAll;

void initSparkLogging(Level level) => libSpark.initSparkLogging(level);

abstract class _SparkIsolate {
Expand Down Expand Up @@ -568,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
Expand Down Expand Up @@ -693,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;
Expand All @@ -708,10 +737,19 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
),
);

final scriptPubKey = btc.Address.addressToOutputScript(
var scriptPubKey = btc.Address.addressToOutputScript(
txData.recipients![i].address,
_bitcoinDartNetwork,
);
if (txData.sparkNameInfo != null) {
final baseScript = scriptPubKey;
scriptPubKey = sparkNameFeeScript(
baseScript: scriptPubKey,
name: txData.sparkNameInfo!.name,
sparkAddress: txData.sparkNameInfo!.sparkAddress.value,
);
sparkNameFeeScriptSizeDelta += scriptPubKey.length - baseScript.length;
}
txb.addOutput(
scriptPubKey,
recipientsWithFeeSubtracted[i].amount.raw.toInt(),
Expand Down Expand Up @@ -816,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) {
Expand Down
6 changes: 3 additions & 3 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1028,9 +1028,9 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "4bd84c88e1b2a817a2604ec53030634cc3304bc7"
resolved-ref: "4bd84c88e1b2a817a2604ec53030634cc3304bc7"
url: "https://github.com/cypherstack/flutter_libsparkmobile.git"
ref: "53db5a06a7b7f3df68fe6263f1453f77513bec06"
resolved-ref: "53db5a06a7b7f3df68fe6263f1453f77513bec06"
url: "https://github.com/firoorg/flutter_libsparkmobile.git"
source: git
version: "0.1.0"
flutter_lints:
Expand Down
4 changes: 2 additions & 2 deletions scripts/app_config/templates/pubspec.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ dependencies:
# %%ENABLE_FIRO%%
# flutter_libsparkmobile:
# git:
# url: https://github.com/cypherstack/flutter_libsparkmobile.git
# ref: 4bd84c88e1b2a817a2604ec53030634cc3304bc7
# url: https://github.com/firoorg/flutter_libsparkmobile.git
# ref: 53db5a06a7b7f3df68fe6263f1453f77513bec06
# %%END_ENABLE_FIRO%%

# %%ENABLE_EPIC%%
Expand Down
45 changes: 45 additions & 0 deletions test/wallets/spark_name_fee_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import 'dart:typed_data';

import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';

void main() {
test('Spark Name validation rejects underscores before construction', () {
final pattern = RegExp(kNameRegexString);
expect(pattern.hasMatch('NAME-FOR.TESTING'), isTrue);
expect(pattern.hasMatch('NAME_FOR_TESTING'), isFalse);
});

test('Spark Name fee output includes the name and address tag', () {
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);
expect(feeScript[25], OP_SPARKNAMEID);
expect(feeScript[32], OP_DROP);
expect(feeScript.last, OP_DROP);
});

test('Spark Name payments never have the miner fee subtracted', () {
expect(
shouldSubtractSparkFeeFromAmount(
isSparkNameRegistration: true,
spendsAll: true,
),
isFalse,
);
expect(
shouldSubtractSparkFeeFromAmount(
isSparkNameRegistration: false,
spendsAll: true,
),
isTrue,
);
});
}
Loading