mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-24 04:19:23 +00:00
litecoin v2 txs
and comment move in particl output checking code
This commit is contained in:
parent
dc9e4d2dc1
commit
b96135f6f9
2 changed files with 237 additions and 19 deletions
|
@ -1,13 +1,17 @@
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
|
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||||
|
import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart';
|
||||||
|
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/utilities/amount/amount.dart';
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/wallets/crypto_currency/coins/litecoin.dart';
|
import 'package:stackwallet/wallets/crypto_currency/coins/litecoin.dart';
|
||||||
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart';
|
import 'package:stackwallet/wallets/crypto_currency/crypto_currency.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/coin_control_interface.dart';
|
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/coin_control_interface.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/ordinals_interface.dart';
|
import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
|
||||||
|
|
||||||
class LitecoinWallet extends Bip39HDWallet
|
class LitecoinWallet extends Bip39HDWallet
|
||||||
with ElectrumXInterface, CoinControlInterface, OrdinalsInterface {
|
with ElectrumXInterface, CoinControlInterface, OrdinalsInterface {
|
||||||
|
@ -46,23 +50,237 @@ class LitecoinWallet extends Bip39HDWallet
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateTransactions() async {
|
Future<void> updateTransactions() async {
|
||||||
final currentChainHeight = await fetchChainHeight();
|
// Get all addresses.
|
||||||
|
List<Address> allAddressesOld = await fetchAddressesForElectrumXScan();
|
||||||
|
|
||||||
// TODO: [prio=med] switch to V2 transactions
|
// Separate receiving and change addresses.
|
||||||
final data = await fetchTransactionsV1(
|
Set<String> receivingAddresses = allAddressesOld
|
||||||
addresses: await fetchAddressesForElectrumXScan(),
|
.where((e) => e.subType == AddressSubType.receiving)
|
||||||
currentChainHeight: currentChainHeight,
|
.map((e) => e.value)
|
||||||
);
|
.toSet();
|
||||||
|
Set<String> changeAddresses = allAddressesOld
|
||||||
|
.where((e) => e.subType == AddressSubType.change)
|
||||||
|
.map((e) => e.value)
|
||||||
|
.toSet();
|
||||||
|
|
||||||
await mainDB.addNewTransactionData(
|
// Remove duplicates.
|
||||||
data
|
final allAddressesSet = {...receivingAddresses, ...changeAddresses};
|
||||||
.map((e) => Tuple2(
|
|
||||||
e.transaction,
|
// Fetch history from ElectrumX.
|
||||||
e.address,
|
final List<Map<String, dynamic>> allTxHashes =
|
||||||
))
|
await fetchHistory(allAddressesSet);
|
||||||
.toList(),
|
|
||||||
walletId,
|
// Only parse new txs (not in db yet).
|
||||||
);
|
List<Map<String, dynamic>> allTransactions = [];
|
||||||
|
for (final txHash in allTxHashes) {
|
||||||
|
// Check for duplicates by searching for tx by tx_hash in db.
|
||||||
|
final storedTx = await mainDB.isar.transactionV2s
|
||||||
|
.where()
|
||||||
|
.txidWalletIdEqualTo(txHash["tx_hash"] as String, walletId)
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
if (storedTx == null ||
|
||||||
|
storedTx.height == null ||
|
||||||
|
(storedTx.height != null && storedTx.height! <= 0)) {
|
||||||
|
// Tx not in db yet.
|
||||||
|
final tx = await electrumXCachedClient.getTransaction(
|
||||||
|
txHash: txHash["tx_hash"] as String,
|
||||||
|
verbose: true,
|
||||||
|
coin: cryptoCurrency.coin,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only tx to list once.
|
||||||
|
if (allTransactions
|
||||||
|
.indexWhere((e) => e["txid"] == tx["txid"] as String) ==
|
||||||
|
-1) {
|
||||||
|
tx["height"] = txHash["height"];
|
||||||
|
allTransactions.add(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse all new txs.
|
||||||
|
final List<TransactionV2> txns = [];
|
||||||
|
for (final txData in allTransactions) {
|
||||||
|
bool wasSentFromThisWallet = false;
|
||||||
|
// Set to true if any inputs were detected as owned by this wallet.
|
||||||
|
|
||||||
|
bool wasReceivedInThisWallet = false;
|
||||||
|
// Set to true if any outputs were detected as owned by this wallet.
|
||||||
|
|
||||||
|
// Parse inputs.
|
||||||
|
BigInt amountReceivedInThisWallet = BigInt.zero;
|
||||||
|
BigInt changeAmountReceivedInThisWallet = BigInt.zero;
|
||||||
|
final List<InputV2> inputs = [];
|
||||||
|
for (final jsonInput in txData["vin"] as List) {
|
||||||
|
final map = Map<String, dynamic>.from(jsonInput as Map);
|
||||||
|
|
||||||
|
final List<String> addresses = [];
|
||||||
|
String valueStringSats = "0";
|
||||||
|
OutpointV2? outpoint;
|
||||||
|
|
||||||
|
final coinbase = map["coinbase"] as String?;
|
||||||
|
|
||||||
|
if (coinbase == null) {
|
||||||
|
// Not a coinbase (ie a typical input).
|
||||||
|
final txid = map["txid"] as String;
|
||||||
|
final vout = map["vout"] as int;
|
||||||
|
|
||||||
|
final inputTx = await electrumXCachedClient.getTransaction(
|
||||||
|
txHash: txid,
|
||||||
|
coin: cryptoCurrency.coin,
|
||||||
|
);
|
||||||
|
|
||||||
|
final prevOutJson = Map<String, dynamic>.from(
|
||||||
|
(inputTx["vout"] as List).firstWhere((e) => e["n"] == vout)
|
||||||
|
as Map);
|
||||||
|
|
||||||
|
final prevOut = OutputV2.fromElectrumXJson(
|
||||||
|
prevOutJson,
|
||||||
|
decimalPlaces: cryptoCurrency.fractionDigits,
|
||||||
|
isFullAmountNotSats: true,
|
||||||
|
walletOwns: false, // Doesn't matter here as this is not saved.
|
||||||
|
);
|
||||||
|
|
||||||
|
outpoint = OutpointV2.isarCantDoRequiredInDefaultConstructor(
|
||||||
|
txid: txid,
|
||||||
|
vout: vout,
|
||||||
|
);
|
||||||
|
valueStringSats = prevOut.valueStringSats;
|
||||||
|
addresses.addAll(prevOut.addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor(
|
||||||
|
scriptSigHex: map["scriptSig"]?["hex"] as String?,
|
||||||
|
sequence: map["sequence"] as int?,
|
||||||
|
outpoint: outpoint,
|
||||||
|
valueStringSats: valueStringSats,
|
||||||
|
addresses: addresses,
|
||||||
|
witness: map["witness"] as String?,
|
||||||
|
coinbase: coinbase,
|
||||||
|
innerRedeemScriptAsm: map["innerRedeemscriptAsm"] as String?,
|
||||||
|
// Need addresses before we can know if the wallet owns this input.
|
||||||
|
walletOwns: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if input was from this wallet.
|
||||||
|
if (allAddressesSet.intersection(input.addresses.toSet()).isNotEmpty) {
|
||||||
|
wasSentFromThisWallet = true;
|
||||||
|
input = input.copyWith(walletOwns: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs.add(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse outputs.
|
||||||
|
final List<OutputV2> outputs = [];
|
||||||
|
for (final outputJson in txData["vout"] as List) {
|
||||||
|
OutputV2 output = OutputV2.fromElectrumXJson(
|
||||||
|
Map<String, dynamic>.from(outputJson as Map),
|
||||||
|
decimalPlaces: cryptoCurrency.fractionDigits,
|
||||||
|
isFullAmountNotSats: true,
|
||||||
|
// Need addresses before we can know if the wallet owns this input.
|
||||||
|
walletOwns: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// If output was to my wallet, add value to amount received.
|
||||||
|
if (receivingAddresses
|
||||||
|
.intersection(output.addresses.toSet())
|
||||||
|
.isNotEmpty) {
|
||||||
|
wasReceivedInThisWallet = true;
|
||||||
|
amountReceivedInThisWallet += output.value;
|
||||||
|
output = output.copyWith(walletOwns: true);
|
||||||
|
} else if (changeAddresses
|
||||||
|
.intersection(output.addresses.toSet())
|
||||||
|
.isNotEmpty) {
|
||||||
|
wasReceivedInThisWallet = true;
|
||||||
|
changeAmountReceivedInThisWallet += output.value;
|
||||||
|
output = output.copyWith(walletOwns: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs.add(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
final totalOut = outputs
|
||||||
|
.map((e) => e.value)
|
||||||
|
.fold(BigInt.zero, (value, element) => value + element);
|
||||||
|
|
||||||
|
TransactionType type;
|
||||||
|
TransactionSubType subType = TransactionSubType.none;
|
||||||
|
|
||||||
|
// At least one input was owned by this wallet.
|
||||||
|
if (wasSentFromThisWallet) {
|
||||||
|
type = TransactionType.outgoing;
|
||||||
|
|
||||||
|
if (wasReceivedInThisWallet) {
|
||||||
|
if (changeAmountReceivedInThisWallet + amountReceivedInThisWallet ==
|
||||||
|
totalOut) {
|
||||||
|
// Definitely sent all to self.
|
||||||
|
type = TransactionType.sentToSelf;
|
||||||
|
} else if (amountReceivedInThisWallet == BigInt.zero) {
|
||||||
|
// Most likely just a typical send, do nothing here yet.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (wasReceivedInThisWallet) {
|
||||||
|
// Only found outputs owned by this wallet.
|
||||||
|
type = TransactionType.incoming;
|
||||||
|
|
||||||
|
// Check for special Litecoin outputs like ordinals.
|
||||||
|
if (outputs.isNotEmpty) {
|
||||||
|
// Iterate through outputs to check for ordinals.
|
||||||
|
for (final output in outputs) {
|
||||||
|
for (final String address in output.addresses) {
|
||||||
|
final inscriptionData = await litescribeAPI
|
||||||
|
.getInscriptionsByAddress(address)
|
||||||
|
.catchError((e) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"Failed to get inscription data for address $address",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if any inscription data matches this output.
|
||||||
|
for (final inscription in inscriptionData) {
|
||||||
|
final txid = inscription.location.split(":").first;
|
||||||
|
if (inscription.address == address &&
|
||||||
|
txid == txData["txid"] as String) {
|
||||||
|
// Found an ordinal.
|
||||||
|
subType = TransactionSubType.ordinal;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// type = TransactionType.ordinal;
|
||||||
|
} else {
|
||||||
|
Logging.instance.log(
|
||||||
|
"Unexpected tx found (ignoring it): $txData",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final tx = TransactionV2(
|
||||||
|
walletId: walletId,
|
||||||
|
blockHash: txData["blockhash"] as String?,
|
||||||
|
hash: txData["hash"] as String,
|
||||||
|
txid: txData["txid"] as String,
|
||||||
|
height: txData["height"] as int?,
|
||||||
|
version: txData["version"] as int,
|
||||||
|
timestamp: txData["blocktime"] as int? ??
|
||||||
|
DateTime.timestamp().millisecondsSinceEpoch ~/ 1000,
|
||||||
|
inputs: List.unmodifiable(inputs),
|
||||||
|
outputs: List.unmodifiable(outputs),
|
||||||
|
type: type,
|
||||||
|
subType: subType,
|
||||||
|
otherData: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
txns.add(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
await mainDB.updateOrPutTransactionV2s(txns);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -260,6 +260,9 @@ class ParticlWallet extends Bip39HDWallet
|
||||||
TransactionType type;
|
TransactionType type;
|
||||||
TransactionSubType subType = TransactionSubType.none;
|
TransactionSubType subType = TransactionSubType.none;
|
||||||
|
|
||||||
|
// Particl has special outputs like confidential amounts. We can check
|
||||||
|
// for them here. They're also checked in checkBlockUTXO.
|
||||||
|
|
||||||
// At least one input was owned by this wallet.
|
// At least one input was owned by this wallet.
|
||||||
if (wasSentFromThisWallet) {
|
if (wasSentFromThisWallet) {
|
||||||
type = TransactionType.outgoing;
|
type = TransactionType.outgoing;
|
||||||
|
@ -272,9 +275,6 @@ class ParticlWallet extends Bip39HDWallet
|
||||||
} else if (amountReceivedInThisWallet == BigInt.zero) {
|
} else if (amountReceivedInThisWallet == BigInt.zero) {
|
||||||
// Most likely just a typical send, do nothing here yet.
|
// Most likely just a typical send, do nothing here yet.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Particl has special outputs like confidential amounts. We can check
|
|
||||||
// for them here. They're also checked in checkBlockUTXO.
|
|
||||||
}
|
}
|
||||||
} else if (wasReceivedInThisWallet) {
|
} else if (wasReceivedInThisWallet) {
|
||||||
// Only found outputs owned by this wallet.
|
// Only found outputs owned by this wallet.
|
||||||
|
|
Loading…
Reference in a new issue