dirty WIP notification tx creation

This commit is contained in:
julian 2023-01-05 12:48:18 -06:00
parent 8c0366904a
commit 915458dbf3
2 changed files with 393 additions and 174 deletions

View file

@ -2,12 +2,19 @@ import 'dart:convert';
import 'dart:typed_data';
import 'package:bip47/bip47.dart';
import 'package:bip47/src/util.dart';
import 'package:bitcoindart/bitcoindart.dart';
import 'package:bitcoindart/src/utils/constants/op.dart' as op;
import 'package:bitcoindart/src/utils/script.dart' as bscript;
import 'package:decimal/decimal.dart';
import 'package:pointycastle/digests/sha256.dart';
import 'package:stackwallet/hive/db.dart';
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/models/paymint/utxo_model.dart';
extension PayNym on DogecoinWallet {
// fetch or generate this wallet's bip47 payment code
Future<PaymentCode> getPaymentCode() async {
@ -48,4 +55,204 @@ extension PayNym on DogecoinWallet {
// Future<Map<String, dynamic>> prepareNotificationTransaction(
// String targetPaymentCode) async {}
Future<String> createNotificationTx(
String targetPaymentCodeString,
List<UtxoObject> utxosToUse,
) async {
final utxoSigningData = await fetchBuildTxData(utxosToUse);
final targetPaymentCode =
PaymentCode.fromPaymentCode(targetPaymentCodeString, network);
final myCode = await getPaymentCode();
final utxo = utxosToUse.first;
final txPoint = utxo.txid.fromHex.toList();
final txPointIndex = utxo.vout;
final rev = Uint8List(txPoint.length + 4);
Util.copyBytes(Uint8List.fromList(txPoint), 0, rev, 0, txPoint.length);
final buffer = rev.buffer.asByteData();
buffer.setUint32(txPoint.length, txPointIndex, Endian.little);
final myKeyPair = utxoSigningData[utxo.txid]["keyPair"] as ECPair;
final S = SecretPoint(
myKeyPair.privateKey!,
targetPaymentCode.notificationPublicKey(),
);
final blindingMask = PaymentCode.getMask(S.ecdhSecret(), rev);
final blindedPaymentCode = PaymentCode.blind(
myCode.getPayload(),
blindingMask,
);
final opReturnScript = bscript.compile([
(op.OPS["OP_RETURN"] as int),
blindedPaymentCode,
]);
final bobP2PKH = P2PKH(
data: PaymentData(
pubkey: targetPaymentCode.notificationPublicKey(),
),
).data;
final notificationScript = bscript.compile([bobP2PKH.output]);
// build a notification tx
final txb = TransactionBuilder();
txb.setVersion(1);
txb.addInput(
"9c6000d597c5008f7bfc2618aed5e4a6ae57677aab95078aae708e1cab11f486",
txPointIndex,
);
txb.addOutput(targetPaymentCode.notificationAddress(),
Format.decimalAmountToSatoshis(Decimal.one, coin));
txb.addOutput(
opReturnScript, Format.decimalAmountToSatoshis(Decimal.one, coin));
txb.sign(
vin: 0,
keyPair: myKeyPair,
);
final builtTx = txb.build();
return builtTx.toHex();
}
}
Future<Map<String, dynamic>> parseTransaction(
Map<String, dynamic> txData,
dynamic electrumxClient,
Set<String> myAddresses,
Set<String> myChangeAddresses,
Coin coin,
int minConfirms,
Decimal currentPrice,
) async {
Set<String> inputAddresses = {};
Set<String> outputAddresses = {};
int totalInputValue = 0;
int totalOutputValue = 0;
int amountSentFromWallet = 0;
int amountReceivedInWallet = 0;
// parse inputs
for (final input in txData["vin"] as List) {
final prevTxid = input["txid"] as String;
final prevOut = input["vout"] as int;
// fetch input tx to get address
final inputTx = await electrumxClient.getTransaction(
txHash: prevTxid,
coin: coin,
);
for (final output in inputTx["vout"] as List) {
// check matching output
if (prevOut == output["n"]) {
// get value
final value = Format.decimalAmountToSatoshis(
Decimal.parse(output["value"].toString()),
coin,
);
// add value to total
totalInputValue += value;
// get input(prevOut) address
final address = output["scriptPubKey"]?["addresses"]?[0] as String? ??
output["scriptPubKey"]?["address"] as String?;
if (address != null) {
inputAddresses.add(address);
// if input was from my wallet, add value to amount sent
if (myAddresses.contains(address)) {
amountSentFromWallet += value;
}
}
}
}
}
// parse outputs
for (final output in txData["vout"] as List) {
// get value
final value = Format.decimalAmountToSatoshis(
Decimal.parse(output["value"].toString()),
coin,
);
// 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 from my wallet, add value to amount received
if (myAddresses.contains(address)) {
amountReceivedInWallet += value;
}
}
}
final mySentFromAddresses = myAddresses.intersection(inputAddresses);
final myReceivedOnAddresses = myAddresses.intersection(outputAddresses);
final fee = totalInputValue - totalOutputValue;
// create normalized tx data map
Map<String, dynamic> normalizedTx = {};
final int confirms = txData["confirmations"] as int? ?? 0;
normalizedTx["txid"] = txData["txid"] as String;
normalizedTx["confirmed_status"] = confirms >= minConfirms;
normalizedTx["confirmations"] = confirms;
normalizedTx["timestamp"] = txData["blocktime"] as int? ??
(DateTime.now().millisecondsSinceEpoch ~/ 1000);
normalizedTx["aliens"] = <dynamic>[];
normalizedTx["fees"] = fee;
normalizedTx["address"] = txData["address"] as String;
normalizedTx["inputSize"] = txData["vin"].length;
normalizedTx["outputSize"] = txData["vout"].length;
normalizedTx["inputs"] = txData["vin"];
normalizedTx["outputs"] = txData["vout"];
normalizedTx["height"] = txData["height"] as int;
int amount;
String type;
if (mySentFromAddresses.isNotEmpty && myReceivedOnAddresses.isNotEmpty) {
// tx is sent to self
type = "Sent to self";
amount = amountReceivedInWallet - amountSentFromWallet;
} else if (mySentFromAddresses.isNotEmpty) {
// outgoing tx
type = "Sent";
amount = amountSentFromWallet;
} else {
// incoming tx
type = "Received";
amount = amountReceivedInWallet;
}
normalizedTx["txType"] = type;
normalizedTx["amount"] = amount;
normalizedTx["worthNow"] = (Format.satoshisToAmount(
amount,
coin: coin,
) *
currentPrice)
.toStringAsFixed(2);
return normalizedTx;
}

View file

@ -20,6 +20,7 @@ import 'package:stackwallet/models/models.dart' as models;
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/paymint/transactions_model.dart';
import 'package:stackwallet/models/paymint/utxo_model.dart';
import 'package:stackwallet/services/coins/coin_paynym_extension.dart';
import 'package:stackwallet/services/coins/coin_service.dart';
import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart';
@ -1063,7 +1064,8 @@ class DogecoinWallet extends CoinServiceAPI {
final priceData =
await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency);
Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero;
final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale;
final locale =
Platform.isWindows ? "en_US" : await Devicelocale.currentLocale;
final String worthNow = Format.localizedStringAsFixed(
value:
((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) /
@ -2091,180 +2093,190 @@ class DogecoinWallet extends CoinServiceAPI {
await fastFetch(vHashes.toList());
for (final txObject in allTransactions) {
List<String> sendersArray = [];
List<String> recipientsArray = [];
// List<String> sendersArray = [];
// List<String> recipientsArray = [];
//
// // Usually only has value when txType = 'Send'
// int inputAmtSentFromWallet = 0;
// // Usually has value regardless of txType due to change addresses
// int outputAmtAddressedToWallet = 0;
// int fee = 0;
//
// Map<String, dynamic> midSortedTx = {};
//
// for (int i = 0; i < (txObject["vin"] as List).length; i++) {
// final input = txObject["vin"][i] as Map;
// final prevTxid = input["txid"] as String;
// final prevOut = input["vout"] as int;
//
// final tx = await _cachedElectrumXClient.getTransaction(
// txHash: prevTxid, coin: coin);
//
// for (final out in tx["vout"] as List) {
// if (prevOut == out["n"]) {
// final address = out["scriptPubKey"]["addresses"][0] as String?;
// if (address != null) {
// sendersArray.add(address);
// }
// }
// }
// }
//
// Logging.instance.log("sendersArray: $sendersArray", level: LogLevel.Info);
//
// for (final output in txObject["vout"] as List) {
// final address = output["scriptPubKey"]["addresses"][0] as String?;
// if (address != null) {
// recipientsArray.add(address);
// }
// }
//
// Logging.instance
// .log("recipientsArray: $recipientsArray", level: LogLevel.Info);
//
// final foundInSenders =
// allAddresses.any((element) => sendersArray.contains(element));
// Logging.instance
// .log("foundInSenders: $foundInSenders", level: LogLevel.Info);
//
// // If txType = Sent, then calculate inputAmtSentFromWallet
// if (foundInSenders) {
// int totalInput = 0;
// for (int i = 0; i < (txObject["vin"] as List).length; i++) {
// final input = txObject["vin"][i] as Map;
// final prevTxid = input["txid"] as String;
// final prevOut = input["vout"] as int;
// final tx = await _cachedElectrumXClient.getTransaction(
// txHash: prevTxid,
// coin: coin,
// );
//
// for (final out in tx["vout"] as List) {
// if (prevOut == out["n"]) {
// inputAmtSentFromWallet +=
// (Decimal.parse(out["value"].toString()) *
// Decimal.fromInt(Constants.satsPerCoin(coin)))
// .toBigInt()
// .toInt();
// }
// }
// }
// totalInput = inputAmtSentFromWallet;
// int totalOutput = 0;
//
// for (final output in txObject["vout"] as List) {
// final address = output["scriptPubKey"]["addresses"][0];
// final value = output["value"];
// final _value = (Decimal.parse(value.toString()) *
// Decimal.fromInt(Constants.satsPerCoin(coin)))
// .toBigInt()
// .toInt();
// totalOutput += _value;
// if (changeAddressesP2PKH.contains(address)) {
// inputAmtSentFromWallet -= _value;
// } else {
// // change address from 'sent from' to the 'sent to' address
// txObject["address"] = address;
// }
// }
// // calculate transaction fee
// fee = totalInput - totalOutput;
// // subtract fee from sent to calculate correct value of sent tx
// inputAmtSentFromWallet -= fee;
// } else {
// // counters for fee calculation
// int totalOut = 0;
// int totalIn = 0;
//
// // add up received tx value
// for (final output in txObject["vout"] as List) {
// final address = output["scriptPubKey"]["addresses"][0];
// if (address != null) {
// final value = (Decimal.parse(output["value"].toString()) *
// Decimal.fromInt(Constants.satsPerCoin(coin)))
// .toBigInt()
// .toInt();
// totalOut += value;
// if (allAddresses.contains(address)) {
// outputAmtAddressedToWallet += value;
// }
// }
// }
//
// // calculate fee for received tx
// for (int i = 0; i < (txObject["vin"] as List).length; i++) {
// final input = txObject["vin"][i] as Map;
// final prevTxid = input["txid"] as String;
// final prevOut = input["vout"] as int;
// final tx = await _cachedElectrumXClient.getTransaction(
// txHash: prevTxid,
// coin: coin,
// );
//
// for (final out in tx["vout"] as List) {
// if (prevOut == out["n"]) {
// totalIn += (Decimal.parse(out["value"].toString()) *
// Decimal.fromInt(Constants.satsPerCoin(coin)))
// .toBigInt()
// .toInt();
// }
// }
// }
// fee = totalIn - totalOut;
// }
//
// // create final tx map
// midSortedTx["txid"] = txObject["txid"];
// midSortedTx["confirmed_status"] = (txObject["confirmations"] != null) &&
// (txObject["confirmations"] as int >= MINIMUM_CONFIRMATIONS);
// midSortedTx["confirmations"] = txObject["confirmations"] ?? 0;
// midSortedTx["timestamp"] = txObject["blocktime"] ??
// (DateTime.now().millisecondsSinceEpoch ~/ 1000);
//
// if (foundInSenders) {
// midSortedTx["txType"] = "Sent";
// midSortedTx["amount"] = inputAmtSentFromWallet;
// final String worthNow =
// ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) /
// Decimal.fromInt(Constants.satsPerCoin(coin)))
// .toDecimal(scaleOnInfinitePrecision: 2)
// .toStringAsFixed(2);
// midSortedTx["worthNow"] = worthNow;
// midSortedTx["worthAtBlockTimestamp"] = worthNow;
// } else {
// midSortedTx["txType"] = "Received";
// midSortedTx["amount"] = outputAmtAddressedToWallet;
// final worthNow =
// ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) /
// Decimal.fromInt(Constants.satsPerCoin(coin)))
// .toDecimal(scaleOnInfinitePrecision: 2)
// .toStringAsFixed(2);
// midSortedTx["worthNow"] = worthNow;
// }
// midSortedTx["aliens"] = <dynamic>[];
// midSortedTx["fees"] = fee;
// midSortedTx["address"] = txObject["address"];
// midSortedTx["inputSize"] = txObject["vin"].length;
// midSortedTx["outputSize"] = txObject["vout"].length;
// midSortedTx["inputs"] = txObject["vin"];
// midSortedTx["outputs"] = txObject["vout"];
//
// final int height = txObject["height"] as int;
// midSortedTx["height"] = height;
//
// if (height >= latestTxnBlockHeight) {
// latestTxnBlockHeight = height;
// }
// Usually only has value when txType = 'Send'
int inputAmtSentFromWallet = 0;
// Usually has value regardless of txType due to change addresses
int outputAmtAddressedToWallet = 0;
int fee = 0;
Map<String, dynamic> midSortedTx = {};
for (int i = 0; i < (txObject["vin"] as List).length; i++) {
final input = txObject["vin"][i] as Map;
final prevTxid = input["txid"] as String;
final prevOut = input["vout"] as int;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: prevTxid, coin: coin);
for (final out in tx["vout"] as List) {
if (prevOut == out["n"]) {
final address = out["scriptPubKey"]["addresses"][0] as String?;
if (address != null) {
sendersArray.add(address);
}
}
}
}
Logging.instance.log("sendersArray: $sendersArray", level: LogLevel.Info);
for (final output in txObject["vout"] as List) {
final address = output["scriptPubKey"]["addresses"][0] as String?;
if (address != null) {
recipientsArray.add(address);
}
}
Logging.instance
.log("recipientsArray: $recipientsArray", level: LogLevel.Info);
final foundInSenders =
allAddresses.any((element) => sendersArray.contains(element));
Logging.instance
.log("foundInSenders: $foundInSenders", level: LogLevel.Info);
// If txType = Sent, then calculate inputAmtSentFromWallet
if (foundInSenders) {
int totalInput = 0;
for (int i = 0; i < (txObject["vin"] as List).length; i++) {
final input = txObject["vin"][i] as Map;
final prevTxid = input["txid"] as String;
final prevOut = input["vout"] as int;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: prevTxid,
coin: coin,
);
for (final out in tx["vout"] as List) {
if (prevOut == out["n"]) {
inputAmtSentFromWallet +=
(Decimal.parse(out["value"].toString()) *
Decimal.fromInt(Constants.satsPerCoin(coin)))
.toBigInt()
.toInt();
}
}
}
totalInput = inputAmtSentFromWallet;
int totalOutput = 0;
for (final output in txObject["vout"] as List) {
final address = output["scriptPubKey"]["addresses"][0];
final value = output["value"];
final _value = (Decimal.parse(value.toString()) *
Decimal.fromInt(Constants.satsPerCoin(coin)))
.toBigInt()
.toInt();
totalOutput += _value;
if (changeAddressesP2PKH.contains(address)) {
inputAmtSentFromWallet -= _value;
} else {
// change address from 'sent from' to the 'sent to' address
txObject["address"] = address;
}
}
// calculate transaction fee
fee = totalInput - totalOutput;
// subtract fee from sent to calculate correct value of sent tx
inputAmtSentFromWallet -= fee;
} else {
// counters for fee calculation
int totalOut = 0;
int totalIn = 0;
// add up received tx value
for (final output in txObject["vout"] as List) {
final address = output["scriptPubKey"]["addresses"][0];
if (address != null) {
final value = (Decimal.parse(output["value"].toString()) *
Decimal.fromInt(Constants.satsPerCoin(coin)))
.toBigInt()
.toInt();
totalOut += value;
if (allAddresses.contains(address)) {
outputAmtAddressedToWallet += value;
}
}
}
// calculate fee for received tx
for (int i = 0; i < (txObject["vin"] as List).length; i++) {
final input = txObject["vin"][i] as Map;
final prevTxid = input["txid"] as String;
final prevOut = input["vout"] as int;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: prevTxid,
coin: coin,
);
for (final out in tx["vout"] as List) {
if (prevOut == out["n"]) {
totalIn += (Decimal.parse(out["value"].toString()) *
Decimal.fromInt(Constants.satsPerCoin(coin)))
.toBigInt()
.toInt();
}
}
}
fee = totalIn - totalOut;
}
// create final tx map
midSortedTx["txid"] = txObject["txid"];
midSortedTx["confirmed_status"] = (txObject["confirmations"] != null) &&
(txObject["confirmations"] as int >= MINIMUM_CONFIRMATIONS);
midSortedTx["confirmations"] = txObject["confirmations"] ?? 0;
midSortedTx["timestamp"] = txObject["blocktime"] ??
(DateTime.now().millisecondsSinceEpoch ~/ 1000);
if (foundInSenders) {
midSortedTx["txType"] = "Sent";
midSortedTx["amount"] = inputAmtSentFromWallet;
final String worthNow =
((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) /
Decimal.fromInt(Constants.satsPerCoin(coin)))
.toDecimal(scaleOnInfinitePrecision: 2)
.toStringAsFixed(2);
midSortedTx["worthNow"] = worthNow;
midSortedTx["worthAtBlockTimestamp"] = worthNow;
} else {
midSortedTx["txType"] = "Received";
midSortedTx["amount"] = outputAmtAddressedToWallet;
final worthNow =
((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) /
Decimal.fromInt(Constants.satsPerCoin(coin)))
.toDecimal(scaleOnInfinitePrecision: 2)
.toStringAsFixed(2);
midSortedTx["worthNow"] = worthNow;
}
midSortedTx["aliens"] = <dynamic>[];
midSortedTx["fees"] = fee;
midSortedTx["address"] = txObject["address"];
midSortedTx["inputSize"] = txObject["vin"].length;
midSortedTx["outputSize"] = txObject["vout"].length;
midSortedTx["inputs"] = txObject["vin"];
midSortedTx["outputs"] = txObject["vout"];
final int height = txObject["height"] as int;
midSortedTx["height"] = height;
if (height >= latestTxnBlockHeight) {
latestTxnBlockHeight = height;
}
final midSortedTx = await parseTransaction(
txObject,
cachedElectrumXClient,
allAddresses.toSet(),
(changeAddressesP2PKH as List<String>).toSet(),
coin,
MINIMUM_CONFIRMATIONS,
currentPrice,
);
midSortedArray.add(midSortedTx);
}