mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-10 20:54:33 +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 'package:isar/isar.dart';
|
||||
|
@ -102,6 +103,15 @@ class TransactionV2 {
|
|||
...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
|
||||
String toString() {
|
||||
return 'TransactionV2(\n'
|
||||
|
@ -116,6 +126,7 @@ class TransactionV2 {
|
|||
' version: $version,\n'
|
||||
' inputs: $inputs,\n'
|
||||
' outputs: $outputs,\n'
|
||||
' otherData: $otherData,\n'
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,17 @@ class TxIcon extends ConsumerWidget {
|
|||
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 (isCancelled) {
|
||||
return assets.receiveCancelled;
|
||||
|
|
|
@ -50,7 +50,9 @@ class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
|
|||
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) {
|
||||
return "Anonymized";
|
||||
} else {
|
||||
|
|
|
@ -95,7 +95,12 @@ class _TransactionV2DetailsViewState
|
|||
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 ||
|
||||
_transaction.type == TransactionType.sentToSelf) {
|
||||
|
|
|
@ -13,11 +13,11 @@ import 'package:stackwallet/utilities/logger.dart';
|
|||
import 'package:stackwallet/utilities/util.dart';
|
||||
import 'package:stackwallet/wallets/crypto_currency/coins/firo.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/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/spark_interface.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
const sparkStartBlock = 819300; // (approx 18 Jan 2024)
|
||||
|
||||
|
@ -60,6 +60,22 @@ class FiroWallet extends Bip39HDWallet
|
|||
final List<Map<String, dynamic>> allTxHashes =
|
||||
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 = [];
|
||||
|
||||
// some lelantus transactions aren't fetched via wallet addresses so they
|
||||
|
@ -108,7 +124,7 @@ class FiroWallet extends Bip39HDWallet
|
|||
if (allTransactions
|
||||
.indexWhere((e) => e["txid"] == tx["txid"] as String) ==
|
||||
-1) {
|
||||
tx["height"] = txHash["height"];
|
||||
tx["height"] ??= txHash["height"];
|
||||
allTransactions.add(tx);
|
||||
}
|
||||
// }
|
||||
|
@ -133,10 +149,6 @@ class FiroWallet extends Bip39HDWallet
|
|||
bool isMasterNodePayment = false;
|
||||
final bool isSparkSpend = txData["type"] == 9 && txData["version"] == 3;
|
||||
|
||||
if (txData.toString().contains("spark")) {
|
||||
Util.printJson(txData);
|
||||
}
|
||||
|
||||
// parse outputs
|
||||
final List<OutputV2> outputs = [];
|
||||
for (final outputJson in txData["vout"] as List) {
|
||||
|
@ -415,605 +427,6 @@ class FiroWallet extends Bip39HDWallet
|
|||
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
|
||||
({String? blockedReason, bool blocked}) checkBlockUTXO(
|
||||
Map<String, dynamic> jsonUTXO,
|
||||
|
|
Loading…
Reference in a new issue