mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-03-25 08:38:45 +00:00
some firo transaction display fixes
This commit is contained in:
parent
2469c3eb91
commit
8336712a23
5 changed files with 49 additions and 607 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
@ -102,6 +103,15 @@ class TransactionV2 {
|
||||||
...outputs.map((e) => e.addresses).expand((e) => e),
|
...outputs.map((e) => e.addresses).expand((e) => e),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Amount? getAnonFee() {
|
||||||
|
try {
|
||||||
|
final map = jsonDecode(otherData!) as Map;
|
||||||
|
return Amount.fromSerializedJsonString(map["anonFees"] as String);
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'TransactionV2(\n'
|
return 'TransactionV2(\n'
|
||||||
|
@ -116,6 +126,7 @@ class TransactionV2 {
|
||||||
' version: $version,\n'
|
' version: $version,\n'
|
||||||
' inputs: $inputs,\n'
|
' inputs: $inputs,\n'
|
||||||
' outputs: $outputs,\n'
|
' outputs: $outputs,\n'
|
||||||
|
' otherData: $otherData,\n'
|
||||||
')';
|
')';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,17 @@ class TxIcon extends ConsumerWidget {
|
||||||
return Assets.svg.anonymize;
|
return Assets.svg.anonymize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subType == TransactionSubType.mint ||
|
||||||
|
subType == TransactionSubType.sparkMint) {
|
||||||
|
if (isCancelled) {
|
||||||
|
return Assets.svg.anonymizeFailed;
|
||||||
|
}
|
||||||
|
if (isPending) {
|
||||||
|
return Assets.svg.anonymizePending;
|
||||||
|
}
|
||||||
|
return Assets.svg.anonymize;
|
||||||
|
}
|
||||||
|
|
||||||
if (isReceived) {
|
if (isReceived) {
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
return assets.receiveCancelled;
|
return assets.receiveCancelled;
|
||||||
|
|
|
@ -50,7 +50,9 @@ class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
|
||||||
ref.read(pWallets).getWallet(walletId).cryptoCurrency.minConfirms,
|
ref.read(pWallets).getWallet(walletId).cryptoCurrency.minConfirms,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (_transaction.subType == TransactionSubType.cashFusion) {
|
if (_transaction.subType == TransactionSubType.cashFusion ||
|
||||||
|
_transaction.subType == TransactionSubType.sparkMint ||
|
||||||
|
_transaction.subType == TransactionSubType.mint) {
|
||||||
if (confirmedStatus) {
|
if (confirmedStatus) {
|
||||||
return "Anonymized";
|
return "Anonymized";
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -95,7 +95,12 @@ class _TransactionV2DetailsViewState
|
||||||
minConfirms =
|
minConfirms =
|
||||||
ref.read(pWallets).getWallet(walletId).cryptoCurrency.minConfirms;
|
ref.read(pWallets).getWallet(walletId).cryptoCurrency.minConfirms;
|
||||||
|
|
||||||
fee = _transaction.getFee(coin: coin);
|
if (_transaction.subType == TransactionSubType.join ||
|
||||||
|
_transaction.subType == TransactionSubType.sparkSpend) {
|
||||||
|
fee = _transaction.getAnonFee()!;
|
||||||
|
} else {
|
||||||
|
fee = _transaction.getFee(coin: coin);
|
||||||
|
}
|
||||||
|
|
||||||
if (_transaction.subType == TransactionSubType.cashFusion ||
|
if (_transaction.subType == TransactionSubType.cashFusion ||
|
||||||
_transaction.type == TransactionType.sentToSelf) {
|
_transaction.type == TransactionType.sentToSelf) {
|
||||||
|
|
|
@ -13,11 +13,11 @@ 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';
|
||||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
||||||
|
import 'package:stackwallet/wallets/isar/models/spark_coin.dart';
|
||||||
import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart';
|
import 'package:stackwallet/wallets/wallet/intermediate/bip39_hd_wallet.dart';
|
||||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart';
|
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart';
|
||||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart';
|
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart';
|
||||||
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
|
||||||
|
|
||||||
const sparkStartBlock = 819300; // (approx 18 Jan 2024)
|
const sparkStartBlock = 819300; // (approx 18 Jan 2024)
|
||||||
|
|
||||||
|
@ -60,6 +60,22 @@ 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
|
||||||
|
.where()
|
||||||
|
.walletIdEqualToAnyLTagHash(walletId)
|
||||||
|
.txHashProperty()
|
||||||
|
.findAll();
|
||||||
|
|
||||||
|
for (final txid in sparkTxids) {
|
||||||
|
// check for duplicates before adding to list
|
||||||
|
if (allTxHashes.indexWhere((e) => e["tx_hash"] == txid) == -1) {
|
||||||
|
final info = {
|
||||||
|
"tx_hash": txid,
|
||||||
|
};
|
||||||
|
allTxHashes.add(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<Map<String, dynamic>> allTransactions = [];
|
List<Map<String, dynamic>> allTransactions = [];
|
||||||
|
|
||||||
// some lelantus transactions aren't fetched via wallet addresses so they
|
// some lelantus transactions aren't fetched via wallet addresses so they
|
||||||
|
@ -108,7 +124,7 @@ class FiroWallet extends Bip39HDWallet
|
||||||
if (allTransactions
|
if (allTransactions
|
||||||
.indexWhere((e) => e["txid"] == tx["txid"] as String) ==
|
.indexWhere((e) => e["txid"] == tx["txid"] as String) ==
|
||||||
-1) {
|
-1) {
|
||||||
tx["height"] = txHash["height"];
|
tx["height"] ??= txHash["height"];
|
||||||
allTransactions.add(tx);
|
allTransactions.add(tx);
|
||||||
}
|
}
|
||||||
// }
|
// }
|
||||||
|
@ -133,10 +149,6 @@ class FiroWallet extends Bip39HDWallet
|
||||||
bool isMasterNodePayment = false;
|
bool isMasterNodePayment = false;
|
||||||
final bool isSparkSpend = txData["type"] == 9 && txData["version"] == 3;
|
final bool isSparkSpend = txData["type"] == 9 && txData["version"] == 3;
|
||||||
|
|
||||||
if (txData.toString().contains("spark")) {
|
|
||||||
Util.printJson(txData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse outputs
|
// parse outputs
|
||||||
final List<OutputV2> outputs = [];
|
final List<OutputV2> outputs = [];
|
||||||
for (final outputJson in txData["vout"] as List) {
|
for (final outputJson in txData["vout"] as List) {
|
||||||
|
@ -415,605 +427,6 @@ class FiroWallet extends Bip39HDWallet
|
||||||
await mainDB.updateOrPutTransactionV2s(txns);
|
await mainDB.updateOrPutTransactionV2s(txns);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateTransactionsOLD() async {
|
|
||||||
final allAddresses = await fetchAddressesForElectrumXScan();
|
|
||||||
|
|
||||||
Set<String> receivingAddresses = allAddresses
|
|
||||||
.where((e) => e.subType == AddressSubType.receiving)
|
|
||||||
.map((e) => e.value)
|
|
||||||
.toSet();
|
|
||||||
Set<String> changeAddresses = allAddresses
|
|
||||||
.where((e) => e.subType == AddressSubType.change)
|
|
||||||
.map((e) => e.value)
|
|
||||||
.toSet();
|
|
||||||
|
|
||||||
final List<Map<String, dynamic>> allTxHashes =
|
|
||||||
await fetchHistory(allAddresses.map((e) => e.value).toList());
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> allTransactions = [];
|
|
||||||
|
|
||||||
// some lelantus transactions aren't fetched via wallet addresses so they
|
|
||||||
// will never show as confirmed in the gui.
|
|
||||||
final unconfirmedTransactions = await mainDB
|
|
||||||
.getTransactions(walletId)
|
|
||||||
.filter()
|
|
||||||
.heightIsNull()
|
|
||||||
.findAll();
|
|
||||||
for (final tx in unconfirmedTransactions) {
|
|
||||||
final txn = await electrumXCachedClient.getTransaction(
|
|
||||||
txHash: tx.txid,
|
|
||||||
verbose: true,
|
|
||||||
coin: info.coin,
|
|
||||||
);
|
|
||||||
final height = txn["height"] as int?;
|
|
||||||
|
|
||||||
if (height != null) {
|
|
||||||
// tx was mined
|
|
||||||
// add to allTxHashes
|
|
||||||
final info = {
|
|
||||||
"tx_hash": tx.txid,
|
|
||||||
"height": height,
|
|
||||||
"address": tx.address.value?.value,
|
|
||||||
};
|
|
||||||
allTxHashes.add(info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// final currentHeight = await chainHeight;
|
|
||||||
|
|
||||||
for (final txHash in allTxHashes) {
|
|
||||||
// final storedTx = await db
|
|
||||||
// .getTransactions(walletId)
|
|
||||||
// .filter()
|
|
||||||
// .txidEqualTo(txHash["tx_hash"] as String)
|
|
||||||
// .findFirst();
|
|
||||||
|
|
||||||
// if (storedTx == null ||
|
|
||||||
// !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) {
|
|
||||||
final tx = await electrumXCachedClient.getTransaction(
|
|
||||||
txHash: txHash["tx_hash"] as String,
|
|
||||||
verbose: true,
|
|
||||||
coin: info.coin,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (allTransactions
|
|
||||||
.indexWhere((e) => e["txid"] == tx["txid"] as String) ==
|
|
||||||
-1) {
|
|
||||||
tx["address"] = await mainDB
|
|
||||||
.getAddresses(walletId)
|
|
||||||
.filter()
|
|
||||||
.valueEqualTo(txHash["address"] as String)
|
|
||||||
.findFirst();
|
|
||||||
tx["height"] = txHash["height"];
|
|
||||||
allTransactions.add(tx);
|
|
||||||
}
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Tuple2<Transaction, Address?>> txnsData = [];
|
|
||||||
|
|
||||||
for (final txObject in allTransactions) {
|
|
||||||
final inputList = txObject["vin"] as List;
|
|
||||||
final outputList = txObject["vout"] as List;
|
|
||||||
|
|
||||||
bool isMint = false;
|
|
||||||
bool isJMint = false;
|
|
||||||
bool isSparkMint = false;
|
|
||||||
bool isSparkSpend = false;
|
|
||||||
|
|
||||||
// check if tx is Mint or jMint
|
|
||||||
for (final output in outputList) {
|
|
||||||
if (output["scriptPubKey"]?["type"] == "lelantusmint") {
|
|
||||||
final asm = output["scriptPubKey"]?["asm"] as String?;
|
|
||||||
if (asm != null) {
|
|
||||||
if (asm.startsWith("OP_LELANTUSJMINT")) {
|
|
||||||
isJMint = true;
|
|
||||||
break;
|
|
||||||
} else if (asm.startsWith("OP_LELANTUSMINT")) {
|
|
||||||
isMint = true;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Unknown mint op code found for lelantusmint tx: ${txObject["txid"]}",
|
|
||||||
level: LogLevel.Error,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Logging.instance.log(
|
|
||||||
"ASM for lelantusmint tx: ${txObject["txid"]} is null!",
|
|
||||||
level: LogLevel.Error,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (output["scriptPubKey"]?["type"] == "sparkmint") {
|
|
||||||
final asm = output["scriptPubKey"]?["asm"] as String?;
|
|
||||||
if (asm != null) {
|
|
||||||
if (asm.startsWith("OP_SPARKMINT")) {
|
|
||||||
isSparkMint = true;
|
|
||||||
break;
|
|
||||||
} else if (asm.startsWith("OP_SPARKSPEND")) {
|
|
||||||
isSparkSpend = true;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Unknown mint op code found for lelantusmint tx: ${txObject["txid"]}",
|
|
||||||
level: LogLevel.Error,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Logging.instance.log(
|
|
||||||
"ASM for sparkmint tx: ${txObject["txid"]} is null!",
|
|
||||||
level: LogLevel.Error,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSparkSpend || isSparkMint) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> inputAddresses = {};
|
|
||||||
Set<String> outputAddresses = {};
|
|
||||||
|
|
||||||
Amount totalInputValue = Amount(
|
|
||||||
rawValue: BigInt.zero,
|
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
|
||||||
);
|
|
||||||
Amount totalOutputValue = Amount(
|
|
||||||
rawValue: BigInt.zero,
|
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
|
||||||
);
|
|
||||||
|
|
||||||
Amount amountSentFromWallet = Amount(
|
|
||||||
rawValue: BigInt.zero,
|
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
|
||||||
);
|
|
||||||
Amount amountReceivedInWallet = Amount(
|
|
||||||
rawValue: BigInt.zero,
|
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
|
||||||
);
|
|
||||||
Amount changeAmount = Amount(
|
|
||||||
rawValue: BigInt.zero,
|
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Parse mint transaction ================================================
|
|
||||||
// We should be able to assume this belongs to this wallet
|
|
||||||
if (isMint) {
|
|
||||||
List<Input> ins = [];
|
|
||||||
|
|
||||||
// Parse inputs
|
|
||||||
for (final input in inputList) {
|
|
||||||
// Both value and address should not be null for a mint
|
|
||||||
final address = input["address"] as String?;
|
|
||||||
final value = input["valueSat"] as int?;
|
|
||||||
|
|
||||||
// We should not need to check whether the mint belongs to this
|
|
||||||
// wallet as any tx we look up will be looked up by one of this
|
|
||||||
// wallet's addresses
|
|
||||||
if (address != null && value != null) {
|
|
||||||
totalInputValue += value.toAmountAsRaw(
|
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ins.add(
|
|
||||||
Input(
|
|
||||||
txid: input['txid'] as String? ?? "",
|
|
||||||
vout: input['vout'] as int? ?? -1,
|
|
||||||
scriptSig: input['scriptSig']?['hex'] as String?,
|
|
||||||
scriptSigAsm: input['scriptSig']?['asm'] as String?,
|
|
||||||
isCoinbase: input['is_coinbase'] as bool?,
|
|
||||||
sequence: input['sequence'] as int?,
|
|
||||||
innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse outputs
|
|
||||||
for (final output in outputList) {
|
|
||||||
// get value
|
|
||||||
final value = Amount.fromDecimal(
|
|
||||||
Decimal.parse(output["value"].toString()),
|
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
|
||||||
);
|
|
||||||
|
|
||||||
// add value to total
|
|
||||||
totalOutputValue += value;
|
|
||||||
}
|
|
||||||
|
|
||||||
final fee = totalInputValue - totalOutputValue;
|
|
||||||
final tx = Transaction(
|
|
||||||
walletId: walletId,
|
|
||||||
txid: txObject["txid"] as String,
|
|
||||||
timestamp: txObject["blocktime"] as int? ??
|
|
||||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
|
||||||
type: TransactionType.sentToSelf,
|
|
||||||
subType: TransactionSubType.mint,
|
|
||||||
amount: totalOutputValue.raw.toInt(),
|
|
||||||
amountString: totalOutputValue.toJsonString(),
|
|
||||||
fee: fee.raw.toInt(),
|
|
||||||
height: txObject["height"] as int?,
|
|
||||||
isCancelled: false,
|
|
||||||
isLelantus: true,
|
|
||||||
slateId: null,
|
|
||||||
otherData: null,
|
|
||||||
nonce: null,
|
|
||||||
inputs: ins,
|
|
||||||
outputs: [],
|
|
||||||
numberOfMessages: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
txnsData.add(Tuple2(tx, null));
|
|
||||||
|
|
||||||
// Otherwise parse JMint transaction ===================================
|
|
||||||
} else if (isJMint) {
|
|
||||||
Amount jMintFees = Amount(
|
|
||||||
rawValue: BigInt.zero,
|
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Parse inputs
|
|
||||||
List<Input> ins = [];
|
|
||||||
for (final input in inputList) {
|
|
||||||
// JMint fee
|
|
||||||
final nFee = Decimal.tryParse(input["nFees"].toString());
|
|
||||||
if (nFee != null) {
|
|
||||||
final fees = Amount.fromDecimal(
|
|
||||||
nFee,
|
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
|
||||||
);
|
|
||||||
|
|
||||||
jMintFees += fees;
|
|
||||||
}
|
|
||||||
|
|
||||||
ins.add(
|
|
||||||
Input(
|
|
||||||
txid: input['txid'] as String? ?? "",
|
|
||||||
vout: input['vout'] as int? ?? -1,
|
|
||||||
scriptSig: input['scriptSig']?['hex'] as String?,
|
|
||||||
scriptSigAsm: input['scriptSig']?['asm'] as String?,
|
|
||||||
isCoinbase: input['is_coinbase'] as bool?,
|
|
||||||
sequence: input['sequence'] as int?,
|
|
||||||
innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool nonWalletAddressFoundInOutputs = false;
|
|
||||||
|
|
||||||
// Parse outputs
|
|
||||||
List<Output> outs = [];
|
|
||||||
for (final output in outputList) {
|
|
||||||
// get value
|
|
||||||
final value = Amount.fromDecimal(
|
|
||||||
Decimal.parse(output["value"].toString()),
|
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
|
||||||
);
|
|
||||||
|
|
||||||
// add value to total
|
|
||||||
totalOutputValue += value;
|
|
||||||
|
|
||||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
|
||||||
output['scriptPubKey']?['address'] as String?;
|
|
||||||
|
|
||||||
if (address != null) {
|
|
||||||
outputAddresses.add(address);
|
|
||||||
if (receivingAddresses.contains(address) ||
|
|
||||||
changeAddresses.contains(address)) {
|
|
||||||
amountReceivedInWallet += value;
|
|
||||||
} else {
|
|
||||||
nonWalletAddressFoundInOutputs = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outs.add(
|
|
||||||
Output(
|
|
||||||
scriptPubKey: output['scriptPubKey']?['hex'] as String?,
|
|
||||||
scriptPubKeyAsm: output['scriptPubKey']?['asm'] as String?,
|
|
||||||
scriptPubKeyType: output['scriptPubKey']?['type'] as String?,
|
|
||||||
scriptPubKeyAddress: address ?? "jmint",
|
|
||||||
value: value.raw.toInt(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final txid = txObject["txid"] as String;
|
|
||||||
|
|
||||||
const subType = TransactionSubType.join;
|
|
||||||
|
|
||||||
final type = nonWalletAddressFoundInOutputs
|
|
||||||
? TransactionType.outgoing
|
|
||||||
: (await mainDB.isar.lelantusCoins
|
|
||||||
.where()
|
|
||||||
.walletIdEqualTo(walletId)
|
|
||||||
.filter()
|
|
||||||
.txidEqualTo(txid)
|
|
||||||
.findFirst()) ==
|
|
||||||
null
|
|
||||||
? TransactionType.incoming
|
|
||||||
: TransactionType.sentToSelf;
|
|
||||||
|
|
||||||
final amount = nonWalletAddressFoundInOutputs
|
|
||||||
? totalOutputValue
|
|
||||||
: amountReceivedInWallet;
|
|
||||||
|
|
||||||
final possibleNonWalletAddresses =
|
|
||||||
receivingAddresses.difference(outputAddresses);
|
|
||||||
final possibleReceivingAddresses =
|
|
||||||
receivingAddresses.intersection(outputAddresses);
|
|
||||||
|
|
||||||
final transactionAddress = nonWalletAddressFoundInOutputs
|
|
||||||
? Address(
|
|
||||||
walletId: walletId,
|
|
||||||
value: possibleNonWalletAddresses.first,
|
|
||||||
derivationIndex: -1,
|
|
||||||
derivationPath: null,
|
|
||||||
type: AddressType.nonWallet,
|
|
||||||
subType: AddressSubType.nonWallet,
|
|
||||||
publicKey: [],
|
|
||||||
)
|
|
||||||
: allAddresses.firstWhere(
|
|
||||||
(e) => e.value == possibleReceivingAddresses.first,
|
|
||||||
);
|
|
||||||
|
|
||||||
final tx = Transaction(
|
|
||||||
walletId: walletId,
|
|
||||||
txid: txid,
|
|
||||||
timestamp: txObject["blocktime"] as int? ??
|
|
||||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
|
||||||
type: type,
|
|
||||||
subType: subType,
|
|
||||||
amount: amount.raw.toInt(),
|
|
||||||
amountString: amount.toJsonString(),
|
|
||||||
fee: jMintFees.raw.toInt(),
|
|
||||||
height: txObject["height"] as int?,
|
|
||||||
isCancelled: false,
|
|
||||||
isLelantus: true,
|
|
||||||
slateId: null,
|
|
||||||
otherData: null,
|
|
||||||
nonce: null,
|
|
||||||
inputs: ins,
|
|
||||||
outputs: outs,
|
|
||||||
numberOfMessages: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
txnsData.add(Tuple2(tx, transactionAddress));
|
|
||||||
|
|
||||||
// Master node payment =====================================
|
|
||||||
} else if (inputList.length == 1 &&
|
|
||||||
inputList.first["coinbase"] is String) {
|
|
||||||
List<Input> ins = [
|
|
||||||
Input(
|
|
||||||
txid: inputList.first["coinbase"] as String,
|
|
||||||
vout: -1,
|
|
||||||
scriptSig: null,
|
|
||||||
scriptSigAsm: null,
|
|
||||||
isCoinbase: true,
|
|
||||||
sequence: inputList.first['sequence'] as int?,
|
|
||||||
innerRedeemScriptAsm: null,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
// parse outputs
|
|
||||||
List<Output> outs = [];
|
|
||||||
for (final output in outputList) {
|
|
||||||
// get value
|
|
||||||
final value = Amount.fromDecimal(
|
|
||||||
Decimal.parse(output["value"].toString()),
|
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
|
||||||
);
|
|
||||||
|
|
||||||
// get output address
|
|
||||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
|
||||||
output["scriptPubKey"]?["address"] as String?;
|
|
||||||
if (address != null) {
|
|
||||||
outputAddresses.add(address);
|
|
||||||
|
|
||||||
// if output was to my wallet, add value to amount received
|
|
||||||
if (receivingAddresses.contains(address)) {
|
|
||||||
amountReceivedInWallet += value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outs.add(
|
|
||||||
Output(
|
|
||||||
scriptPubKey: output['scriptPubKey']?['hex'] as String?,
|
|
||||||
scriptPubKeyAsm: output['scriptPubKey']?['asm'] as String?,
|
|
||||||
scriptPubKeyType: output['scriptPubKey']?['type'] as String?,
|
|
||||||
scriptPubKeyAddress: address ?? "",
|
|
||||||
value: value.raw.toInt(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is the address initially used to fetch the txid
|
|
||||||
Address transactionAddress = txObject["address"] as Address;
|
|
||||||
|
|
||||||
final tx = Transaction(
|
|
||||||
walletId: walletId,
|
|
||||||
txid: txObject["txid"] as String,
|
|
||||||
timestamp: txObject["blocktime"] as int? ??
|
|
||||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
|
||||||
type: TransactionType.incoming,
|
|
||||||
subType: TransactionSubType.none,
|
|
||||||
// amount may overflow. Deprecated. Use amountString
|
|
||||||
amount: amountReceivedInWallet.raw.toInt(),
|
|
||||||
amountString: amountReceivedInWallet.toJsonString(),
|
|
||||||
fee: 0,
|
|
||||||
height: txObject["height"] as int?,
|
|
||||||
isCancelled: false,
|
|
||||||
isLelantus: false,
|
|
||||||
slateId: null,
|
|
||||||
otherData: null,
|
|
||||||
nonce: null,
|
|
||||||
inputs: ins,
|
|
||||||
outputs: outs,
|
|
||||||
numberOfMessages: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
txnsData.add(Tuple2(tx, transactionAddress));
|
|
||||||
|
|
||||||
// Assume non lelantus transaction =====================================
|
|
||||||
} else {
|
|
||||||
// parse inputs
|
|
||||||
List<Input> ins = [];
|
|
||||||
for (final input in inputList) {
|
|
||||||
final valueSat = input["valueSat"] as int?;
|
|
||||||
final address = input["address"] as String? ??
|
|
||||||
input["scriptPubKey"]?["address"] as String? ??
|
|
||||||
input["scriptPubKey"]?["addresses"]?[0] as String?;
|
|
||||||
|
|
||||||
if (address != null && valueSat != null) {
|
|
||||||
final value = valueSat.toAmountAsRaw(
|
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
|
||||||
);
|
|
||||||
|
|
||||||
// add value to total
|
|
||||||
totalInputValue += value;
|
|
||||||
inputAddresses.add(address);
|
|
||||||
|
|
||||||
// if input was from my wallet, add value to amount sent
|
|
||||||
if (receivingAddresses.contains(address) ||
|
|
||||||
changeAddresses.contains(address)) {
|
|
||||||
amountSentFromWallet += value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input['txid'] == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ins.add(
|
|
||||||
Input(
|
|
||||||
txid: input['txid'] as String,
|
|
||||||
vout: input['vout'] as int? ?? -1,
|
|
||||||
scriptSig: input['scriptSig']?['hex'] as String?,
|
|
||||||
scriptSigAsm: input['scriptSig']?['asm'] as String?,
|
|
||||||
isCoinbase: input['is_coinbase'] as bool?,
|
|
||||||
sequence: input['sequence'] as int?,
|
|
||||||
innerRedeemScriptAsm: input['innerRedeemscriptAsm'] as String?,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse outputs
|
|
||||||
List<Output> outs = [];
|
|
||||||
for (final output in outputList) {
|
|
||||||
// get value
|
|
||||||
final value = Amount.fromDecimal(
|
|
||||||
Decimal.parse(output["value"].toString()),
|
|
||||||
fractionDigits: cryptoCurrency.fractionDigits,
|
|
||||||
);
|
|
||||||
|
|
||||||
// add value to total
|
|
||||||
totalOutputValue += value;
|
|
||||||
|
|
||||||
// get output address
|
|
||||||
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
|
||||||
output["scriptPubKey"]?["address"] as String?;
|
|
||||||
if (address != null) {
|
|
||||||
outputAddresses.add(address);
|
|
||||||
|
|
||||||
// if output was to my wallet, add value to amount received
|
|
||||||
if (receivingAddresses.contains(address)) {
|
|
||||||
amountReceivedInWallet += value;
|
|
||||||
} else if (changeAddresses.contains(address)) {
|
|
||||||
changeAmount += value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outs.add(
|
|
||||||
Output(
|
|
||||||
scriptPubKey: output['scriptPubKey']?['hex'] as String?,
|
|
||||||
scriptPubKeyAsm: output['scriptPubKey']?['asm'] as String?,
|
|
||||||
scriptPubKeyType: output['scriptPubKey']?['type'] as String?,
|
|
||||||
scriptPubKeyAddress: address ?? "",
|
|
||||||
value: value.raw.toInt(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final mySentFromAddresses = [
|
|
||||||
...receivingAddresses.intersection(inputAddresses),
|
|
||||||
...changeAddresses.intersection(inputAddresses)
|
|
||||||
];
|
|
||||||
final myReceivedOnAddresses =
|
|
||||||
receivingAddresses.intersection(outputAddresses);
|
|
||||||
final myChangeReceivedOnAddresses =
|
|
||||||
changeAddresses.intersection(outputAddresses);
|
|
||||||
|
|
||||||
final fee = totalInputValue - totalOutputValue;
|
|
||||||
|
|
||||||
// this is the address initially used to fetch the txid
|
|
||||||
Address transactionAddress = txObject["address"] as Address;
|
|
||||||
|
|
||||||
TransactionType type;
|
|
||||||
Amount amount;
|
|
||||||
if (mySentFromAddresses.isNotEmpty &&
|
|
||||||
myReceivedOnAddresses.isNotEmpty) {
|
|
||||||
// tx is sent to self
|
|
||||||
type = TransactionType.sentToSelf;
|
|
||||||
|
|
||||||
// should be 0
|
|
||||||
amount = amountSentFromWallet -
|
|
||||||
amountReceivedInWallet -
|
|
||||||
fee -
|
|
||||||
changeAmount;
|
|
||||||
} else if (mySentFromAddresses.isNotEmpty) {
|
|
||||||
// outgoing tx
|
|
||||||
type = TransactionType.outgoing;
|
|
||||||
amount = amountSentFromWallet - changeAmount - fee;
|
|
||||||
|
|
||||||
final possible =
|
|
||||||
outputAddresses.difference(myChangeReceivedOnAddresses).first;
|
|
||||||
|
|
||||||
if (transactionAddress.value != possible) {
|
|
||||||
transactionAddress = Address(
|
|
||||||
walletId: walletId,
|
|
||||||
value: possible,
|
|
||||||
derivationIndex: -1,
|
|
||||||
derivationPath: null,
|
|
||||||
subType: AddressSubType.nonWallet,
|
|
||||||
type: AddressType.nonWallet,
|
|
||||||
publicKey: [],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// incoming tx
|
|
||||||
type = TransactionType.incoming;
|
|
||||||
amount = amountReceivedInWallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
final tx = Transaction(
|
|
||||||
walletId: walletId,
|
|
||||||
txid: txObject["txid"] as String,
|
|
||||||
timestamp: txObject["blocktime"] as int? ??
|
|
||||||
(DateTime.now().millisecondsSinceEpoch ~/ 1000),
|
|
||||||
type: type,
|
|
||||||
subType: TransactionSubType.none,
|
|
||||||
// amount may overflow. Deprecated. Use amountString
|
|
||||||
amount: amount.raw.toInt(),
|
|
||||||
amountString: amount.toJsonString(),
|
|
||||||
fee: fee.raw.toInt(),
|
|
||||||
height: txObject["height"] as int?,
|
|
||||||
isCancelled: false,
|
|
||||||
isLelantus: false,
|
|
||||||
slateId: null,
|
|
||||||
otherData: null,
|
|
||||||
nonce: null,
|
|
||||||
inputs: ins,
|
|
||||||
outputs: outs,
|
|
||||||
numberOfMessages: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
txnsData.add(Tuple2(tx, transactionAddress));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await mainDB.addNewTransactionData(txnsData, walletId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
({String? blockedReason, bool blocked}) checkBlockUTXO(
|
({String? blockedReason, bool blocked}) checkBlockUTXO(
|
||||||
Map<String, dynamic> jsonUTXO,
|
Map<String, dynamic> jsonUTXO,
|
||||||
|
|
Loading…
Reference in a new issue