Skip to content

Commit b4bd97f

Browse files
Merge pull request #1413 from reubenyap/codex/firo-spark-name-format
[codex] Fix Firo Spark Name v2.1 registration
2 parents a6a72c1 + a9d36e1 commit b4bd97f

4 files changed

Lines changed: 89 additions & 6 deletions

File tree

lib/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'dart:isolate';
44
import 'dart:math';
55

66
import 'package:bitcoindart/bitcoindart.dart' as btc;
7+
import 'package:bitcoindart/src/utils/script.dart' as bscript;
78
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
89
import 'package:decimal/decimal.dart';
910
import 'package:flutter/foundation.dart';
@@ -50,6 +51,8 @@ const SPARK_OUT_LIMIT_PER_TX = 16;
5051
const OP_SPARKMINT = 0xd1;
5152
const OP_SPARKSMINT = 0xd2;
5253
const OP_SPARKSPEND = 0xd3;
54+
const OP_SPARKNAMEID = 0xe1;
55+
const OP_DROP = 0x75;
5356

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

67+
@visibleForTesting
68+
Uint8List sparkNameFeeScript({
69+
required Uint8List baseScript,
70+
required String name,
71+
required String sparkAddress,
72+
}) => Uint8List.fromList([
73+
...baseScript,
74+
...bscript.compile([
75+
OP_SPARKNAMEID,
76+
Uint8List.fromList(utf8.encode(name)),
77+
OP_DROP,
78+
Uint8List.fromList(utf8.encode(sparkAddress)),
79+
OP_DROP,
80+
]),
81+
]);
82+
83+
@visibleForTesting
84+
bool shouldSubtractSparkFeeFromAmount({
85+
required bool isSparkNameRegistration,
86+
required bool spendsAll,
87+
}) => !isSparkNameRegistration && spendsAll;
88+
6489
void initSparkLogging(Level level) => libSpark.initSparkLogging(level);
6590

6691
abstract class _SparkIsolate {
@@ -568,7 +593,10 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
568593
throw Exception("Insufficient Spark balance");
569594
}
570595

571-
final bool isSendAll = available == txAmount;
596+
final bool isSendAll = shouldSubtractSparkFeeFromAmount(
597+
isSparkNameRegistration: txData.sparkNameInfo != null,
598+
spendsAll: available == txAmount,
599+
);
572600

573601
// prepare coin data for ffi
574602
final serializedCoins = coins
@@ -693,6 +721,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
693721
final List<InputV2> tempInputs = [];
694722
final List<OutputV2> tempOutputs = [];
695723

724+
var sparkNameFeeScriptSizeDelta = 0;
696725
for (int i = 0; i < (txData.recipients?.length ?? 0); i++) {
697726
if (txData.recipients![i].amount.raw == BigInt.zero) {
698727
continue;
@@ -708,10 +737,19 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
708737
),
709738
);
710739

711-
final scriptPubKey = btc.Address.addressToOutputScript(
740+
var scriptPubKey = btc.Address.addressToOutputScript(
712741
txData.recipients![i].address,
713742
_bitcoinDartNetwork,
714743
);
744+
if (txData.sparkNameInfo != null) {
745+
final baseScript = scriptPubKey;
746+
scriptPubKey = sparkNameFeeScript(
747+
baseScript: scriptPubKey,
748+
name: txData.sparkNameInfo!.name,
749+
sparkAddress: txData.sparkNameInfo!.sparkAddress.value,
750+
);
751+
sparkNameFeeScriptSizeDelta += scriptPubKey.length - baseScript.length;
752+
}
715753
txb.addOutput(
716754
scriptPubKey,
717755
recipientsWithFeeSubtracted[i].amount.raw.toInt(),
@@ -816,7 +854,7 @@ mixin SparkInterface<T extends ElectrumXCurrencyInterface>
816854
txHash: extractedTx.getHash(),
817855
additionalTxSize: txData.sparkNameInfo == null
818856
? 0
819-
: noProofNameTxData!.size,
857+
: noProofNameTxData!.size + sparkNameFeeScriptSizeDelta,
820858
));
821859

822860
for (final outputScript in spend.outputScripts) {

pubspec.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,8 +1028,8 @@ packages:
10281028
dependency: "direct main"
10291029
description:
10301030
path: "."
1031-
ref: "4bd84c88e1b2a817a2604ec53030634cc3304bc7"
1032-
resolved-ref: "4bd84c88e1b2a817a2604ec53030634cc3304bc7"
1031+
ref: "783bd00f0114b007f7ef97017cd10ad263ed452c"
1032+
resolved-ref: "783bd00f0114b007f7ef97017cd10ad263ed452c"
10331033
url: "https://github.com/cypherstack/flutter_libsparkmobile.git"
10341034
source: git
10351035
version: "0.1.0"

scripts/app_config/templates/pubspec.template.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ dependencies:
4444
# flutter_libsparkmobile:
4545
# git:
4646
# url: https://github.com/cypherstack/flutter_libsparkmobile.git
47-
# ref: 4bd84c88e1b2a817a2604ec53030634cc3304bc7
47+
# ref: 783bd00f0114b007f7ef97017cd10ad263ed452c
4848
# %%END_ENABLE_FIRO%%
4949

5050
# %%ENABLE_EPIC%%
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import 'dart:typed_data';
2+
3+
import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
4+
import 'package:flutter_test/flutter_test.dart';
5+
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
6+
7+
void main() {
8+
test('Spark Name validation rejects underscores before construction', () {
9+
final pattern = RegExp(kNameRegexString);
10+
expect(pattern.hasMatch('NAME-FOR.TESTING'), isTrue);
11+
expect(pattern.hasMatch('NAME_FOR_TESTING'), isFalse);
12+
});
13+
14+
test('Spark Name fee output includes the name and address tag', () {
15+
final baseScript = Uint8List(25);
16+
final feeScript = sparkNameFeeScript(
17+
baseScript: baseScript,
18+
name: 'alice',
19+
sparkAddress: List.filled(144, 'a').join(),
20+
);
21+
22+
expect(feeScript.length - baseScript.length, 155);
23+
expect(feeScript.length, 180);
24+
expect(feeScript[25], OP_SPARKNAMEID);
25+
expect(feeScript[32], OP_DROP);
26+
expect(feeScript.last, OP_DROP);
27+
});
28+
29+
test('Spark Name payments never have the miner fee subtracted', () {
30+
expect(
31+
shouldSubtractSparkFeeFromAmount(
32+
isSparkNameRegistration: true,
33+
spendsAll: true,
34+
),
35+
isFalse,
36+
);
37+
expect(
38+
shouldSubtractSparkFeeFromAmount(
39+
isSparkNameRegistration: false,
40+
spendsAll: true,
41+
),
42+
isTrue,
43+
);
44+
});
45+
}

0 commit comments

Comments
 (0)