mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-25 11:45:59 +00:00
WIP handle spark transaction parsing
This commit is contained in:
parent
4074023a88
commit
f697aeb043
2 changed files with 128 additions and 32 deletions
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:decimal/decimal.dart';
|
import 'package:decimal/decimal.dart';
|
||||||
|
import 'package:flutter_libsparkmobile/flutter_libsparkmobile.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:stackwallet/db/hive/db.dart';
|
import 'package:stackwallet/db/hive/db.dart';
|
||||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
|
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||||
|
@ -9,6 +10,7 @@ import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart
|
||||||
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
|
import 'package:stackwallet/utilities/extensions/extensions.dart';
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/util.dart';
|
import 'package:stackwallet/utilities/util.dart';
|
||||||
import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart';
|
import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart';
|
||||||
|
@ -60,17 +62,20 @@ class FiroWallet extends Bip39HDWallet
|
||||||
final List<Map<String, dynamic>> allTxHashes =
|
final List<Map<String, dynamic>> allTxHashes =
|
||||||
await fetchHistory(allAddressesSet);
|
await fetchHistory(allAddressesSet);
|
||||||
|
|
||||||
final sparkTxids = await mainDB.isar.sparkCoins
|
final sparkCoins = await mainDB.isar.sparkCoins
|
||||||
.where()
|
.where()
|
||||||
.walletIdEqualToAnyLTagHash(walletId)
|
.walletIdEqualToAnyLTagHash(walletId)
|
||||||
.txHashProperty()
|
|
||||||
.findAll();
|
.findAll();
|
||||||
|
|
||||||
for (final txid in sparkTxids) {
|
final Set<String> sparkTxids = {};
|
||||||
|
|
||||||
|
for (final coin in sparkCoins) {
|
||||||
|
sparkTxids.add(coin.txHash);
|
||||||
// check for duplicates before adding to list
|
// check for duplicates before adding to list
|
||||||
if (allTxHashes.indexWhere((e) => e["tx_hash"] == txid) == -1) {
|
if (allTxHashes.indexWhere((e) => e["tx_hash"] == coin.txHash) == -1) {
|
||||||
final info = {
|
final info = {
|
||||||
"tx_hash": txid,
|
"tx_hash": coin.txHash,
|
||||||
|
"height": coin.height,
|
||||||
};
|
};
|
||||||
allTxHashes.add(info);
|
allTxHashes.add(info);
|
||||||
}
|
}
|
||||||
|
@ -148,6 +153,17 @@ class FiroWallet extends Bip39HDWallet
|
||||||
bool isSparkMint = false;
|
bool isSparkMint = false;
|
||||||
bool isMasterNodePayment = false;
|
bool isMasterNodePayment = false;
|
||||||
final bool isSparkSpend = txData["type"] == 9 && txData["version"] == 3;
|
final bool isSparkSpend = txData["type"] == 9 && txData["version"] == 3;
|
||||||
|
final bool isMySpark = sparkTxids.contains(txData["txid"] as String);
|
||||||
|
|
||||||
|
final sparkCoinsInvolved =
|
||||||
|
sparkCoins.where((e) => e.txHash == txData["txid"]);
|
||||||
|
if (isMySpark && sparkCoinsInvolved.isEmpty) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"sparkCoinsInvolved is empty and should not be! (ignoring tx parsing)",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// parse outputs
|
// parse outputs
|
||||||
final List<OutputV2> outputs = [];
|
final List<OutputV2> outputs = [];
|
||||||
|
@ -173,10 +189,12 @@ class FiroWallet extends Bip39HDWallet
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (outMap["scriptPubKey"]?["type"] == "sparkmint") {
|
if (outMap["scriptPubKey"]?["type"] == "sparkmint" ||
|
||||||
|
outMap["scriptPubKey"]?["type"] == "sparksmint") {
|
||||||
final asm = outMap["scriptPubKey"]?["asm"] as String?;
|
final asm = outMap["scriptPubKey"]?["asm"] as String?;
|
||||||
if (asm != null) {
|
if (asm != null) {
|
||||||
if (asm.startsWith("OP_SPARKMINT")) {
|
if (asm.startsWith("OP_SPARKMINT") ||
|
||||||
|
asm.startsWith("OP_SPARKSMINT")) {
|
||||||
isSparkMint = true;
|
isSparkMint = true;
|
||||||
} else {
|
} else {
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
|
@ -192,16 +210,6 @@ class FiroWallet extends Bip39HDWallet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSparkSpend) {
|
|
||||||
// TODO
|
|
||||||
} else if (isSparkMint) {
|
|
||||||
// TODO
|
|
||||||
} else if (isMint || isJMint) {
|
|
||||||
// do nothing extra ?
|
|
||||||
} else {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputV2 output = OutputV2.fromElectrumXJson(
|
OutputV2 output = OutputV2.fromElectrumXJson(
|
||||||
outMap,
|
outMap,
|
||||||
decimalPlaces: cryptoCurrency.fractionDigits,
|
decimalPlaces: cryptoCurrency.fractionDigits,
|
||||||
|
@ -210,6 +218,46 @@ class FiroWallet extends Bip39HDWallet
|
||||||
walletOwns: false,
|
walletOwns: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// if (isSparkSpend) {
|
||||||
|
// // TODO?
|
||||||
|
// } else
|
||||||
|
if (isSparkMint) {
|
||||||
|
if (isMySpark) {
|
||||||
|
if (output.addresses.isEmpty &&
|
||||||
|
output.scriptPubKeyHex.length >= 488) {
|
||||||
|
// likely spark related
|
||||||
|
final opByte = output.scriptPubKeyHex
|
||||||
|
.substring(0, 2)
|
||||||
|
.toUint8ListFromHex
|
||||||
|
.first;
|
||||||
|
if (opByte == OP_SPARKMINT || opByte == OP_SPARKSMINT) {
|
||||||
|
final serCoin = base64Encode(output.scriptPubKeyHex
|
||||||
|
.substring(2, 488)
|
||||||
|
.toUint8ListFromHex);
|
||||||
|
final coin = sparkCoinsInvolved
|
||||||
|
.where((e) => e.serializedCoinB64!.startsWith(serCoin))
|
||||||
|
.firstOrNull;
|
||||||
|
|
||||||
|
if (coin == null) {
|
||||||
|
// not ours
|
||||||
|
} else {
|
||||||
|
output = output.copyWith(
|
||||||
|
walletOwns: true,
|
||||||
|
valueStringSats: coin.value.toString(),
|
||||||
|
addresses: [
|
||||||
|
coin.address,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isMint || isJMint) {
|
||||||
|
// do nothing extra ?
|
||||||
|
} else {
|
||||||
|
// TODO?
|
||||||
|
}
|
||||||
|
|
||||||
// if output was to my wallet, add value to amount received
|
// if output was to my wallet, add value to amount received
|
||||||
if (receivingAddresses
|
if (receivingAddresses
|
||||||
.intersection(output.addresses.toSet())
|
.intersection(output.addresses.toSet())
|
||||||
|
@ -223,6 +271,13 @@ class FiroWallet extends Bip39HDWallet
|
||||||
wasReceivedInThisWallet = true;
|
wasReceivedInThisWallet = true;
|
||||||
changeAmountReceivedInThisWallet += output.value;
|
changeAmountReceivedInThisWallet += output.value;
|
||||||
output = output.copyWith(walletOwns: true);
|
output = output.copyWith(walletOwns: true);
|
||||||
|
} else if (isSparkMint && isMySpark) {
|
||||||
|
wasReceivedInThisWallet = true;
|
||||||
|
if (output.addresses.contains(sparkChangeAddress)) {
|
||||||
|
changeAmountReceivedInThisWallet += output.value;
|
||||||
|
} else {
|
||||||
|
amountReceivedInThisWallet += output.value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outputs.add(output);
|
outputs.add(output);
|
||||||
|
@ -333,6 +388,32 @@ class FiroWallet extends Bip39HDWallet
|
||||||
if (allAddressesSet.intersection(input.addresses.toSet()).isNotEmpty) {
|
if (allAddressesSet.intersection(input.addresses.toSet()).isNotEmpty) {
|
||||||
wasSentFromThisWallet = true;
|
wasSentFromThisWallet = true;
|
||||||
input = input.copyWith(walletOwns: true);
|
input = input.copyWith(walletOwns: true);
|
||||||
|
} else if (isMySpark) {
|
||||||
|
final lTags = map["lTags"] as List?;
|
||||||
|
|
||||||
|
if (lTags?.isNotEmpty == true) {
|
||||||
|
final List<SparkCoin> usedCoins = [];
|
||||||
|
for (final tag in lTags!) {
|
||||||
|
final components = (tag as String).split(",");
|
||||||
|
final x = components[0].substring(1);
|
||||||
|
final y = components[1].substring(0, components[1].length - 1);
|
||||||
|
|
||||||
|
final hash = LibSpark.hashTag(x, y);
|
||||||
|
usedCoins.addAll(sparkCoins.where((e) => e.lTagHash == hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usedCoins.isNotEmpty) {
|
||||||
|
input = input.copyWith(
|
||||||
|
addresses: usedCoins.map((e) => e.address).toList(),
|
||||||
|
valueStringSats: usedCoins
|
||||||
|
.map((e) => e.value)
|
||||||
|
.reduce((value, element) => value += element)
|
||||||
|
.toString(),
|
||||||
|
walletOwns: true,
|
||||||
|
);
|
||||||
|
wasSentFromThisWallet = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inputs.add(input);
|
inputs.add(input);
|
||||||
|
@ -365,25 +446,10 @@ class FiroWallet extends Bip39HDWallet
|
||||||
totalOut) {
|
totalOut) {
|
||||||
// definitely sent all to self
|
// definitely sent all to self
|
||||||
type = TransactionType.sentToSelf;
|
type = TransactionType.sentToSelf;
|
||||||
} else if (isSparkMint) {
|
|
||||||
// probably sent to self
|
|
||||||
type = TransactionType.sentToSelf;
|
|
||||||
} else if (amountReceivedInThisWallet == BigInt.zero) {
|
} else if (amountReceivedInThisWallet == BigInt.zero) {
|
||||||
// most likely just a typical send
|
// most likely just a typical send
|
||||||
// do nothing here yet
|
// do nothing here yet
|
||||||
}
|
}
|
||||||
|
|
||||||
// check vout 0 for special scripts
|
|
||||||
if (outputs.isNotEmpty) {
|
|
||||||
final output = outputs.first;
|
|
||||||
|
|
||||||
// // check for fusion
|
|
||||||
// if (BchUtils.isFUZE(output.scriptPubKeyHex.toUint8ListFromHex)) {
|
|
||||||
// subType = TransactionSubType.cashFusion;
|
|
||||||
// } else {
|
|
||||||
// // check other cases here such as SLP or cash tokens etc
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (wasReceivedInThisWallet) {
|
} else if (wasReceivedInThisWallet) {
|
||||||
// only found outputs owned by this wallet
|
// only found outputs owned by this wallet
|
||||||
|
|
|
@ -23,6 +23,8 @@ const kDefaultSparkIndex = 1;
|
||||||
// TODO dart style constants. Maybe move to spark lib?
|
// TODO dart style constants. Maybe move to spark lib?
|
||||||
const MAX_STANDARD_TX_WEIGHT = 400000;
|
const MAX_STANDARD_TX_WEIGHT = 400000;
|
||||||
|
|
||||||
|
const SPARK_CHANGE_D = 0x270F;
|
||||||
|
|
||||||
//https://github.com/firoorg/sparkmobile/blob/ef2e39aae18ecc49e0ddc63a3183e9764b96012e/include/spark.h#L16
|
//https://github.com/firoorg/sparkmobile/blob/ef2e39aae18ecc49e0ddc63a3183e9764b96012e/include/spark.h#L16
|
||||||
const SPARK_OUT_LIMIT_PER_TX = 16;
|
const SPARK_OUT_LIMIT_PER_TX = 16;
|
||||||
|
|
||||||
|
@ -31,6 +33,16 @@ const OP_SPARKSMINT = 0xd2;
|
||||||
const OP_SPARKSPEND = 0xd3;
|
const OP_SPARKSPEND = 0xd3;
|
||||||
|
|
||||||
mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
|
mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
|
||||||
|
String? _sparkChangeAddressCached;
|
||||||
|
|
||||||
|
/// Spark change address. Should generally not be exposed to end users.
|
||||||
|
String get sparkChangeAddress {
|
||||||
|
if (_sparkChangeAddressCached == null) {
|
||||||
|
throw Exception("_sparkChangeAddressCached was not initialized");
|
||||||
|
}
|
||||||
|
return _sparkChangeAddressCached!;
|
||||||
|
}
|
||||||
|
|
||||||
static bool validateSparkAddress({
|
static bool validateSparkAddress({
|
||||||
required String address,
|
required String address,
|
||||||
required bool isTestNet,
|
required bool isTestNet,
|
||||||
|
@ -45,6 +57,24 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
|
||||||
await mainDB.putAddress(address);
|
await mainDB.putAddress(address);
|
||||||
} // TODO add other address types to wallet info?
|
} // TODO add other address types to wallet info?
|
||||||
|
|
||||||
|
if (_sparkChangeAddressCached == null) {
|
||||||
|
final root = await getRootHDNode();
|
||||||
|
final String derivationPath;
|
||||||
|
if (cryptoCurrency.network == CryptoCurrencyNetwork.test) {
|
||||||
|
derivationPath = "$kSparkBaseDerivationPathTestnet$kDefaultSparkIndex";
|
||||||
|
} else {
|
||||||
|
derivationPath = "$kSparkBaseDerivationPath$kDefaultSparkIndex";
|
||||||
|
}
|
||||||
|
final keys = root.derivePath(derivationPath);
|
||||||
|
|
||||||
|
_sparkChangeAddressCached = await LibSpark.getAddress(
|
||||||
|
privateKey: keys.privateKey.data,
|
||||||
|
index: kDefaultSparkIndex,
|
||||||
|
diversifier: SPARK_CHANGE_D,
|
||||||
|
isTestNet: cryptoCurrency.network == CryptoCurrencyNetwork.test,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// await info.updateReceivingAddress(
|
// await info.updateReceivingAddress(
|
||||||
// newAddress: address.value,
|
// newAddress: address.value,
|
||||||
// isar: mainDB.isar,
|
// isar: mainDB.isar,
|
||||||
|
|
Loading…
Reference in a new issue