fix bug that can cause transaction broadcast to fail in the case where two or more input utxos come from the same parent transaction

This commit is contained in:
julian 2023-03-09 13:49:39 -06:00
parent 260771061c
commit c04723840f
6 changed files with 676 additions and 1094 deletions

View file

@ -18,6 +18,7 @@ import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/signing_data.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';
@ -2384,7 +2385,6 @@ class BitcoinWallet extends CoinServiceAPI
.log("Attempting to send all $coin", level: LogLevel.Info);
final int vSizeForOneOutput = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
@ -2402,7 +2402,6 @@ class BitcoinWallet extends CoinServiceAPI
final int amount = satoshiAmountToSend - feeForOneOutput;
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: [amount],
@ -2413,7 +2412,7 @@ class BitcoinWallet extends CoinServiceAPI
"recipientAmt": amount,
"fee": feeForOneOutput,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
@ -2421,7 +2420,6 @@ class BitcoinWallet extends CoinServiceAPI
final int vSizeForOneOutput;
try {
vSizeForOneOutput = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
@ -2434,7 +2432,6 @@ class BitcoinWallet extends CoinServiceAPI
final int vSizeForTwoOutPuts;
try {
vSizeForTwoOutPuts = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [
recipientAddress,
@ -2505,7 +2502,6 @@ class BitcoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2533,7 +2529,6 @@ class BitcoinWallet extends CoinServiceAPI
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
level: LogLevel.Info);
txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2546,7 +2541,7 @@ class BitcoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": feeBeingPaid,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
} else {
@ -2563,7 +2558,6 @@ class BitcoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2574,7 +2568,7 @@ class BitcoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": satoshisBeingUsed - satoshiAmountToSend,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
@ -2593,7 +2587,6 @@ class BitcoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2604,7 +2597,7 @@ class BitcoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": satoshisBeingUsed - satoshiAmountToSend,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
@ -2623,7 +2616,6 @@ class BitcoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2634,7 +2626,7 @@ class BitcoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": feeForOneOutput,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
} else {
@ -2660,241 +2652,129 @@ class BitcoinWallet extends CoinServiceAPI
}
}
Future<Map<String, dynamic>> fetchBuildTxData(
Future<List<SigningData>> fetchBuildTxData(
List<isar_models.UTXO> utxosToUse,
) async {
// return data
Map<String, dynamic> results = {};
Map<String, List<String>> addressTxid = {};
// addresses to check
List<String> addressesP2PKH = [];
List<String> addressesP2SH = [];
List<String> addressesP2WPKH = [];
List<SigningData> signingData = [];
try {
// Populating the addresses to check
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: txid,
coin: coin,
);
for (final output in tx["vout"] as List) {
final n = output["n"];
if (n != null && n == utxosToUse[i].vout) {
final address = output["scriptPubKey"]["address"] as String;
if (!addressTxid.containsKey(address)) {
addressTxid[address] = <String>[];
}
(addressTxid[address] as List).add(txid);
switch (addressType(address: address)) {
case DerivePathType.bip44:
addressesP2PKH.add(address);
break;
case DerivePathType.bip49:
addressesP2SH.add(address);
break;
case DerivePathType.bip84:
addressesP2WPKH.add(address);
break;
default:
throw Exception("DerivePathType unsupported");
if (utxosToUse[i].address == null) {
final txid = utxosToUse[i].txid;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: txid,
coin: coin,
);
for (final output in tx["vout"] as List) {
final n = output["n"];
if (n != null && n == utxosToUse[i].vout) {
utxosToUse[i] = utxosToUse[i].copyWith(
address: output["scriptPubKey"]?["addresses"]?[0] as String? ??
output["scriptPubKey"]["address"] as String,
);
}
}
}
final derivePathType = addressType(address: utxosToUse[i].address!);
signingData.add(
SigningData(
derivePathType: derivePathType,
utxo: utxosToUse[i],
),
);
}
// p2pkh / bip44
final p2pkhLength = addressesP2PKH.length;
if (p2pkhLength > 0) {
final receiveDerivations = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bip44,
);
final changeDerivations = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bip44,
);
for (int i = 0; i < p2pkhLength; i++) {
// receives
final receiveDerivation = receiveDerivations[addressesP2PKH[i]];
// if a match exists it will not be null
if (receiveDerivation != null) {
final data = P2PKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["pubKey"] as String)),
network: _network,
).data;
Map<DerivePathType, Map<String, dynamic>> receiveDerivations = {};
Map<DerivePathType, Map<String, dynamic>> changeDerivations = {};
for (String tx in addressTxid[addressesP2PKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
receiveDerivation["wif"] as String,
network: _network,
),
};
}
} else {
// if its not a receive, check change
final changeDerivation = changeDerivations[addressesP2PKH[i]];
// if a match exists it will not be null
if (changeDerivation != null) {
final data = P2PKH(
for (final sd in signingData) {
String? pubKey;
String? wif;
// fetch receiving derivations if null
receiveDerivations[sd.derivePathType] ??= await _fetchDerivations(
chain: 0,
derivePathType: sd.derivePathType,
);
final receiveDerivation =
receiveDerivations[sd.derivePathType]![sd.utxo.address!];
if (receiveDerivation != null) {
pubKey = receiveDerivation["pubKey"] as String;
wif = receiveDerivation["wif"] as String;
} else {
// fetch change derivations if null
changeDerivations[sd.derivePathType] ??= await _fetchDerivations(
chain: 1,
derivePathType: sd.derivePathType,
);
final changeDerivation =
changeDerivations[sd.derivePathType]![sd.utxo.address!];
if (changeDerivation != null) {
pubKey = changeDerivation["pubKey"] as String;
wif = changeDerivation["wif"] as String;
}
}
if (wif != null && pubKey != null) {
final PaymentData data;
final Uint8List? redeemScript;
switch (sd.derivePathType) {
case DerivePathType.bip44:
data = P2PKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
changeDerivation["pubKey"] as String)),
pubkey: Format.stringToUint8List(pubKey),
),
network: _network,
).data;
redeemScript = null;
break;
for (String tx in addressTxid[addressesP2PKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: _network,
),
};
}
}
}
}
}
// p2sh / bip49
final p2shLength = addressesP2SH.length;
if (p2shLength > 0) {
final receiveDerivations = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bip49,
);
final changeDerivations = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bip49,
);
for (int i = 0; i < p2shLength; i++) {
// receives
final receiveDerivation = receiveDerivations[addressesP2SH[i]];
// if a match exists it will not be null
if (receiveDerivation != null) {
final p2wpkh = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["pubKey"] as String)),
network: _network)
.data;
final redeemScript = p2wpkh.output;
final data =
P2SH(data: PaymentData(redeem: p2wpkh), network: _network).data;
for (String tx in addressTxid[addressesP2SH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
receiveDerivation["wif"] as String,
network: _network,
),
"redeemScript": redeemScript,
};
}
} else {
// if its not a receive, check change
final changeDerivation = changeDerivations[addressesP2SH[i]];
// if a match exists it will not be null
if (changeDerivation != null) {
case DerivePathType.bip49:
final p2wpkh = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
changeDerivation["pubKey"] as String)),
network: _network)
.data;
final redeemScript = p2wpkh.output;
final data =
P2SH(data: PaymentData(redeem: p2wpkh), network: _network)
.data;
for (String tx in addressTxid[addressesP2SH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: _network,
),
"redeemScript": redeemScript,
};
}
}
}
}
}
// p2wpkh / bip84
final p2wpkhLength = addressesP2WPKH.length;
if (p2wpkhLength > 0) {
final receiveDerivations = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bip84,
);
final changeDerivations = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bip84,
);
for (int i = 0; i < p2wpkhLength; i++) {
// receives
final receiveDerivation = receiveDerivations[addressesP2WPKH[i]];
// if a match exists it will not be null
if (receiveDerivation != null) {
final data = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["pubKey"] as String)),
network: _network,
).data;
for (String tx in addressTxid[addressesP2WPKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
receiveDerivation["wif"] as String,
network: _network,
),
};
}
} else {
// if its not a receive, check change
final changeDerivation = changeDerivations[addressesP2WPKH[i]];
// if a match exists it will not be null
if (changeDerivation != null) {
final data = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
changeDerivation["pubKey"] as String)),
pubkey: Format.stringToUint8List(pubKey),
),
network: _network,
).data;
redeemScript = p2wpkh.output;
data = P2SH(
data: PaymentData(redeem: p2wpkh),
network: _network,
).data;
break;
for (String tx in addressTxid[addressesP2WPKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: _network,
),
};
}
}
case DerivePathType.bip84:
data = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(pubKey),
),
network: _network,
).data;
redeemScript = null;
break;
default:
throw Exception("DerivePathType unsupported");
}
final keyPair = ECPair.fromWIF(
wif,
network: _network,
);
sd.redeemScript = redeemScript;
sd.output = data.output;
sd.keyPair = keyPair;
}
}
return results;
return signingData;
} catch (e, s) {
Logging.instance
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
@ -2904,8 +2784,7 @@ class BitcoinWallet extends CoinServiceAPI
/// Builds and signs a transaction
Future<Map<String, dynamic>> buildTransaction({
required List<isar_models.UTXO> utxosToUse,
required Map<String, dynamic> utxoSigningData,
required List<SigningData> utxoSigningData,
required List<String> recipients,
required List<int> satoshiAmounts,
}) async {
@ -2916,10 +2795,14 @@ class BitcoinWallet extends CoinServiceAPI
txb.setVersion(1);
// Add transaction inputs
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
txb.addInput(txid, utxosToUse[i].vout, null,
utxoSigningData[txid]["output"] as Uint8List);
for (var i = 0; i < utxoSigningData.length; i++) {
final txid = utxoSigningData[i].utxo.txid;
txb.addInput(
txid,
utxoSigningData[i].utxo.vout,
null,
utxoSigningData[i].output!,
);
}
// Add transaction output
@ -2929,13 +2812,12 @@ class BitcoinWallet extends CoinServiceAPI
try {
// Sign the transaction accordingly
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
for (var i = 0; i < utxoSigningData.length; i++) {
txb.sign(
vin: i,
keyPair: utxoSigningData[txid]["keyPair"] as ECPair,
witnessValue: utxosToUse[i].value,
redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?,
keyPair: utxoSigningData[i].keyPair!,
witnessValue: utxoSigningData[i].utxo.value,
redeemScript: utxoSigningData[i].redeemScript,
);
}
} catch (e, s) {

View file

@ -18,6 +18,7 @@ import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/signing_data.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';
@ -2152,7 +2153,6 @@ class DogecoinWallet extends CoinServiceAPI
.log("Attempting to send all $coin", level: LogLevel.Info);
final int vSizeForOneOutput = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
@ -2167,7 +2167,6 @@ class DogecoinWallet extends CoinServiceAPI
final int amount = satoshiAmountToSend - feeForOneOutput;
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: [amount],
@ -2178,19 +2177,17 @@ class DogecoinWallet extends CoinServiceAPI
"recipientAmt": amount,
"fee": feeForOneOutput,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
final int vSizeForOneOutput = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
final int vSizeForTwoOutPuts = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [
recipientAddress,
@ -2270,7 +2267,6 @@ class DogecoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2298,7 +2294,6 @@ class DogecoinWallet extends CoinServiceAPI
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
level: LogLevel.Info);
txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2311,7 +2306,7 @@ class DogecoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": feeBeingPaid,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
} else {
@ -2328,7 +2323,6 @@ class DogecoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2339,7 +2333,7 @@ class DogecoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": satoshisBeingUsed - satoshiAmountToSend,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
@ -2358,7 +2352,6 @@ class DogecoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2369,7 +2362,7 @@ class DogecoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": satoshisBeingUsed - satoshiAmountToSend,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
@ -2388,7 +2381,6 @@ class DogecoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2399,7 +2391,7 @@ class DogecoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": feeForOneOutput,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
} else {
@ -2425,103 +2417,105 @@ class DogecoinWallet extends CoinServiceAPI
}
}
Future<Map<String, dynamic>> fetchBuildTxData(
Future<List<SigningData>> fetchBuildTxData(
List<isar_models.UTXO> utxosToUse,
) async {
// return data
Map<String, dynamic> results = {};
Map<String, List<String>> addressTxid = {};
// addresses to check
List<String> addressesP2PKH = [];
List<SigningData> signingData = [];
try {
// Populating the addresses to check
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: txid,
coin: coin,
);
for (final output in tx["vout"] as List) {
final n = output["n"];
if (n != null && n == utxosToUse[i].vout) {
final address = output["scriptPubKey"]["addresses"][0] as String;
if (!addressTxid.containsKey(address)) {
addressTxid[address] = <String>[];
}
(addressTxid[address] as List).add(txid);
switch (addressType(address: address)) {
case DerivePathType.bip44:
addressesP2PKH.add(address);
break;
default:
throw Exception("Unsupported DerivePathType");
if (utxosToUse[i].address == null) {
final txid = utxosToUse[i].txid;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: txid,
coin: coin,
);
for (final output in tx["vout"] as List) {
final n = output["n"];
if (n != null && n == utxosToUse[i].vout) {
utxosToUse[i] = utxosToUse[i].copyWith(
address: output["scriptPubKey"]?["addresses"]?[0] as String? ??
output["scriptPubKey"]["address"] as String,
);
}
}
}
final derivePathType = addressType(address: utxosToUse[i].address!);
signingData.add(
SigningData(
derivePathType: derivePathType,
utxo: utxosToUse[i],
),
);
}
// p2pkh / bip44
final p2pkhLength = addressesP2PKH.length;
if (p2pkhLength > 0) {
final receiveDerivations = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bip44,
);
final changeDerivations = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bip44,
);
for (int i = 0; i < p2pkhLength; i++) {
// receives
final receiveDerivation = receiveDerivations[addressesP2PKH[i]];
// if a match exists it will not be null
if (receiveDerivation != null) {
final data = P2PKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["pubKey"] as String)),
network: network,
).data;
Map<DerivePathType, Map<String, dynamic>> receiveDerivations = {};
Map<DerivePathType, Map<String, dynamic>> changeDerivations = {};
for (String tx in addressTxid[addressesP2PKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
receiveDerivation["wif"] as String,
network: network,
),
};
}
} else {
// if its not a receive, check change
final changeDerivation = changeDerivations[addressesP2PKH[i]];
// if a match exists it will not be null
if (changeDerivation != null) {
final data = P2PKH(
for (final sd in signingData) {
String? pubKey;
String? wif;
// fetch receiving derivations if null
receiveDerivations[sd.derivePathType] ??= await _fetchDerivations(
chain: 0,
derivePathType: sd.derivePathType,
);
final receiveDerivation =
receiveDerivations[sd.derivePathType]![sd.utxo.address!];
if (receiveDerivation != null) {
pubKey = receiveDerivation["pubKey"] as String;
wif = receiveDerivation["wif"] as String;
} else {
// fetch change derivations if null
changeDerivations[sd.derivePathType] ??= await _fetchDerivations(
chain: 1,
derivePathType: sd.derivePathType,
);
final changeDerivation =
changeDerivations[sd.derivePathType]![sd.utxo.address!];
if (changeDerivation != null) {
pubKey = changeDerivation["pubKey"] as String;
wif = changeDerivation["wif"] as String;
}
}
if (wif != null && pubKey != null) {
final PaymentData data;
final Uint8List? redeemScript;
switch (sd.derivePathType) {
case DerivePathType.bip44:
data = P2PKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
changeDerivation["pubKey"] as String)),
pubkey: Format.stringToUint8List(pubKey),
),
network: network,
).data;
redeemScript = null;
break;
for (String tx in addressTxid[addressesP2PKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: network,
),
};
}
}
default:
throw Exception("DerivePathType unsupported");
}
final keyPair = ECPair.fromWIF(
wif,
network: network,
);
sd.redeemScript = redeemScript;
sd.output = data.output;
sd.keyPair = keyPair;
}
}
return results;
return signingData;
} catch (e, s) {
Logging.instance
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
@ -2531,8 +2525,7 @@ class DogecoinWallet extends CoinServiceAPI
/// Builds and signs a transaction
Future<Map<String, dynamic>> buildTransaction({
required List<isar_models.UTXO> utxosToUse,
required Map<String, dynamic> utxoSigningData,
required List<SigningData> utxoSigningData,
required List<String> recipients,
required List<int> satoshiAmounts,
}) async {
@ -2543,10 +2536,14 @@ class DogecoinWallet extends CoinServiceAPI
txb.setVersion(1);
// Add transaction inputs
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
txb.addInput(txid, utxosToUse[i].vout, null,
utxoSigningData[txid]["output"] as Uint8List);
for (var i = 0; i < utxoSigningData.length; i++) {
final txid = utxoSigningData[i].utxo.txid;
txb.addInput(
txid,
utxoSigningData[i].utxo.vout,
null,
utxoSigningData[i].output!,
);
}
// Add transaction output
@ -2556,13 +2553,12 @@ class DogecoinWallet extends CoinServiceAPI
try {
// Sign the transaction accordingly
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
for (var i = 0; i < utxoSigningData.length; i++) {
txb.sign(
vin: i,
keyPair: utxoSigningData[txid]["keyPair"] as ECPair,
witnessValue: utxosToUse[i].value,
redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?,
keyPair: utxoSigningData[i].keyPair!,
witnessValue: utxoSigningData[i].utxo.value,
redeemScript: utxoSigningData[i].redeemScript,
);
}
} catch (e, s) {

View file

@ -18,6 +18,7 @@ import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
import 'package:stackwallet/models/lelantus_coin.dart';
import 'package:stackwallet/models/lelantus_fee_data.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/signing_data.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';
@ -36,6 +37,7 @@ import 'package:stackwallet/utilities/bip32_utils.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
import 'package:stackwallet/utilities/format.dart';
@ -1367,7 +1369,6 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
.log("Attempting to send all $coin", level: LogLevel.Info);
final int vSizeForOneOutput = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [_recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
@ -1383,7 +1384,6 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
final int amount = satoshiAmountToSend - feeForOneOutput;
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: [amount],
@ -1399,13 +1399,11 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
}
final int vSizeForOneOutput = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [_recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
final int vSizeForTwoOutPuts = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [
_recipientAddress,
@ -1484,7 +1482,6 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
Logging.instance
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -1512,7 +1509,6 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
level: LogLevel.Info);
txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -1541,7 +1537,6 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -1570,7 +1565,6 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -1599,7 +1593,6 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -1629,119 +1622,126 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
}
}
Future<Map<String, dynamic>> fetchBuildTxData(
Future<List<SigningData>> fetchBuildTxData(
List<isar_models.UTXO> utxosToUse,
) async {
// return data
Map<String, dynamic> results = {};
Map<String, List<String>> addressTxid = {};
// addresses to check
List<String> addresses = [];
List<SigningData> signingData = [];
try {
// Populating the addresses to check
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: txid,
coin: coin,
if (utxosToUse[i].address == null) {
final txid = utxosToUse[i].txid;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: txid,
coin: coin,
);
for (final output in tx["vout"] as List) {
final n = output["n"];
if (n != null && n == utxosToUse[i].vout) {
utxosToUse[i] = utxosToUse[i].copyWith(
address: output["scriptPubKey"]?["addresses"]?[0] as String? ??
output["scriptPubKey"]["address"] as String,
);
}
}
}
signingData.add(
SigningData(
derivePathType: DerivePathType.bip44,
utxo: utxosToUse[i],
),
);
}
Map<DerivePathType, Map<String, dynamic>> receiveDerivations = {};
Map<DerivePathType, Map<String, dynamic>> changeDerivations = {};
for (final sd in signingData) {
String? pubKey;
String? wif;
// fetch receiving derivations if null
receiveDerivations[sd.derivePathType] ??= Map<String, dynamic>.from(
jsonDecode((await _secureStore.read(
key: "${walletId}_receiveDerivations",
)) ??
"{}") as Map,
);
for (final output in tx["vout"] as List) {
final n = output["n"];
if (n != null && n == utxosToUse[i].vout) {
final address = output["scriptPubKey"]["addresses"][0] as String;
if (!addressTxid.containsKey(address)) {
addressTxid[address] = <String>[];
}
(addressTxid[address] as List).add(txid);
addresses.add(address);
dynamic receiveDerivation;
for (int j = 0;
j < receiveDerivations[sd.derivePathType]!.length &&
receiveDerivation == null;
j++) {
if (receiveDerivations[sd.derivePathType]!["$j"]["address"] ==
sd.utxo.address!) {
receiveDerivation = receiveDerivations[sd.derivePathType]!["$j"];
}
}
}
// p2pkh / bip44
final addressesLength = addresses.length;
if (addressesLength > 0) {
final receiveDerivationsString =
await _secureStore.read(key: "${walletId}_receiveDerivations");
final receiveDerivations = Map<String, dynamic>.from(
jsonDecode(receiveDerivationsString ?? "{}") as Map);
if (receiveDerivation != null) {
pubKey = receiveDerivation["publicKey"] as String;
wif = receiveDerivation["wif"] as String;
} else {
// fetch change derivations if null
changeDerivations[sd.derivePathType] ??= Map<String, dynamic>.from(
jsonDecode((await _secureStore.read(
key: "${walletId}_changeDerivations",
)) ??
"{}") as Map,
);
final changeDerivationsString =
await _secureStore.read(key: "${walletId}_changeDerivations");
final changeDerivations = Map<String, dynamic>.from(
jsonDecode(changeDerivationsString ?? "{}") as Map);
for (int i = 0; i < addressesLength; i++) {
// receives
dynamic receiveDerivation;
for (int j = 0; j < receiveDerivations.length; j++) {
if (receiveDerivations["$j"]["address"] == addresses[i]) {
receiveDerivation = receiveDerivations["$j"];
dynamic changeDerivation;
for (int j = 0;
j < changeDerivations[sd.derivePathType]!.length &&
changeDerivation == null;
j++) {
if (changeDerivations[sd.derivePathType]!["$j"]["address"] ==
sd.utxo.address!) {
changeDerivation = changeDerivations[sd.derivePathType]!["$j"];
}
}
// receiveDerivation = receiveDerivations[addresses[i]];
// if a match exists it will not be null
if (receiveDerivation != null) {
final data = P2PKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["publicKey"] as String)),
network: _network,
).data;
if (changeDerivation != null) {
pubKey = changeDerivation["publicKey"] as String;
wif = changeDerivation["wif"] as String;
}
}
for (String tx in addressTxid[addresses[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
receiveDerivation["wif"] as String,
network: _network,
),
};
}
} else {
// if its not a receive, check change
if (wif != null && pubKey != null) {
final PaymentData data;
final Uint8List? redeemScript;
dynamic changeDerivation;
for (int j = 0; j < changeDerivations.length; j++) {
if (changeDerivations["$j"]["address"] == addresses[i]) {
changeDerivation = changeDerivations["$j"];
}
}
// final changeDerivation = changeDerivations[addresses[i]];
// if a match exists it will not be null
if (changeDerivation != null) {
final data = P2PKH(
switch (sd.derivePathType) {
case DerivePathType.bip44:
data = P2PKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
changeDerivation["publicKey"] as String)),
pubkey: Format.stringToUint8List(pubKey),
),
network: _network,
).data;
redeemScript = null;
break;
for (String tx in addressTxid[addresses[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: _network,
),
};
}
}
default:
throw Exception("DerivePathType unsupported");
}
final keyPair = ECPair.fromWIF(
wif,
network: _network,
);
sd.redeemScript = redeemScript;
sd.output = data.output;
sd.keyPair = keyPair;
}
}
return results;
return signingData;
} catch (e, s) {
Logging.instance
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
@ -1751,8 +1751,7 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
/// Builds and signs a transaction
Future<Map<String, dynamic>> buildTransaction({
required List<isar_models.UTXO> utxosToUse,
required Map<String, dynamic> utxoSigningData,
required List<SigningData> utxoSigningData,
required List<String> recipients,
required List<int> satoshiAmounts,
}) async {
@ -1763,10 +1762,14 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
txb.setVersion(1);
// Add transaction inputs
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
txb.addInput(txid, utxosToUse[i].vout, null,
utxoSigningData[txid]["output"] as Uint8List);
for (var i = 0; i < utxoSigningData.length; i++) {
final txid = utxoSigningData[i].utxo.txid;
txb.addInput(
txid,
utxoSigningData[i].utxo.vout,
null,
utxoSigningData[i].output!,
);
}
// Add transaction output
@ -1776,13 +1779,12 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
try {
// Sign the transaction accordingly
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
for (var i = 0; i < utxoSigningData.length; i++) {
txb.sign(
vin: i,
keyPair: utxoSigningData[txid]["keyPair"] as ECPair,
witnessValue: utxosToUse[i].value,
redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?,
keyPair: utxoSigningData[i].keyPair!,
witnessValue: utxoSigningData[i].utxo.value,
redeemScript: utxoSigningData[i].redeemScript,
);
}
} catch (e, s) {

View file

@ -17,6 +17,7 @@ import 'package:stackwallet/electrumx_rpc/electrumx.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/signing_data.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';
@ -2338,7 +2339,6 @@ class LitecoinWallet extends CoinServiceAPI
.log("Attempting to send all $coin", level: LogLevel.Info);
final int vSizeForOneOutput = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
@ -2356,7 +2356,6 @@ class LitecoinWallet extends CoinServiceAPI
final int amount = satoshiAmountToSend - feeForOneOutput;
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: [amount],
@ -2367,19 +2366,17 @@ class LitecoinWallet extends CoinServiceAPI
"recipientAmt": amount,
"fee": feeForOneOutput,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
final int vSizeForOneOutput = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
final int vSizeForTwoOutPuts = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [
recipientAddress,
@ -2445,7 +2442,6 @@ class LitecoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2473,7 +2469,6 @@ class LitecoinWallet extends CoinServiceAPI
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
level: LogLevel.Info);
txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2486,7 +2481,7 @@ class LitecoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": feeBeingPaid,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
} else {
@ -2503,7 +2498,6 @@ class LitecoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2514,7 +2508,7 @@ class LitecoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": satoshisBeingUsed - satoshiAmountToSend,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
@ -2533,7 +2527,6 @@ class LitecoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2544,7 +2537,7 @@ class LitecoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": satoshisBeingUsed - satoshiAmountToSend,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
@ -2563,7 +2556,6 @@ class LitecoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2574,7 +2566,7 @@ class LitecoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": feeForOneOutput,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
} else {
@ -2600,245 +2592,131 @@ class LitecoinWallet extends CoinServiceAPI
}
}
Future<Map<String, dynamic>> fetchBuildTxData(
Future<List<SigningData>> fetchBuildTxData(
List<isar_models.UTXO> utxosToUse,
) async {
// return data
Map<String, dynamic> results = {};
Map<String, List<String>> addressTxid = {};
// addresses to check
List<String> addressesP2PKH = [];
List<String> addressesP2SH = [];
List<String> addressesP2WPKH = [];
List<SigningData> signingData = [];
try {
// Populating the addresses to check
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: txid,
coin: coin,
);
for (final output in tx["vout"] as List) {
final n = output["n"];
if (n != null && n == utxosToUse[i].vout) {
final address = output["scriptPubKey"]["addresses"][0] as String;
if (!addressTxid.containsKey(address)) {
addressTxid[address] = <String>[];
}
(addressTxid[address] as List).add(txid);
switch (addressType(address: address)) {
case DerivePathType.bip44:
addressesP2PKH.add(address);
break;
case DerivePathType.bip49:
addressesP2SH.add(address);
break;
case DerivePathType.bip84:
addressesP2WPKH.add(address);
break;
default:
throw Exception("DerivePathType unsupported");
if (utxosToUse[i].address == null) {
final txid = utxosToUse[i].txid;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: txid,
coin: coin,
);
for (final output in tx["vout"] as List) {
final n = output["n"];
if (n != null && n == utxosToUse[i].vout) {
utxosToUse[i] = utxosToUse[i].copyWith(
address: output["scriptPubKey"]?["addresses"]?[0] as String? ??
output["scriptPubKey"]["address"] as String,
);
}
}
}
final derivePathType = addressType(address: utxosToUse[i].address!);
signingData.add(
SigningData(
derivePathType: derivePathType,
utxo: utxosToUse[i],
),
);
}
// p2pkh / bip44
final p2pkhLength = addressesP2PKH.length;
if (p2pkhLength > 0) {
final receiveDerivations = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bip44,
);
final changeDerivations = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bip44,
);
for (int i = 0; i < p2pkhLength; i++) {
// receives
final receiveDerivation = receiveDerivations[addressesP2PKH[i]];
// if a match exists it will not be null
if (receiveDerivation != null) {
final data = P2PKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["pubKey"] as String)),
network: _network,
).data;
Map<DerivePathType, Map<String, dynamic>> receiveDerivations = {};
Map<DerivePathType, Map<String, dynamic>> changeDerivations = {};
for (String tx in addressTxid[addressesP2PKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
receiveDerivation["wif"] as String,
network: _network,
),
};
}
} else {
// if its not a receive, check change
final changeDerivation = changeDerivations[addressesP2PKH[i]];
// if a match exists it will not be null
if (changeDerivation != null) {
final data = P2PKH(
for (final sd in signingData) {
String? pubKey;
String? wif;
// fetch receiving derivations if null
receiveDerivations[sd.derivePathType] ??= await _fetchDerivations(
chain: 0,
derivePathType: sd.derivePathType,
);
final receiveDerivation =
receiveDerivations[sd.derivePathType]![sd.utxo.address!];
if (receiveDerivation != null) {
pubKey = receiveDerivation["pubKey"] as String;
wif = receiveDerivation["wif"] as String;
} else {
// fetch change derivations if null
changeDerivations[sd.derivePathType] ??= await _fetchDerivations(
chain: 1,
derivePathType: sd.derivePathType,
);
final changeDerivation =
changeDerivations[sd.derivePathType]![sd.utxo.address!];
if (changeDerivation != null) {
pubKey = changeDerivation["pubKey"] as String;
wif = changeDerivation["wif"] as String;
}
}
if (wif != null && pubKey != null) {
final PaymentData data;
final Uint8List? redeemScript;
switch (sd.derivePathType) {
case DerivePathType.bip44:
data = P2PKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
changeDerivation["pubKey"] as String)),
pubkey: Format.stringToUint8List(pubKey),
),
network: _network,
).data;
redeemScript = null;
break;
for (String tx in addressTxid[addressesP2PKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: _network,
),
};
}
}
}
}
}
// p2sh / bip49
final p2shLength = addressesP2SH.length;
if (p2shLength > 0) {
final receiveDerivations = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bip49,
);
final changeDerivations = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bip49,
);
for (int i = 0; i < p2shLength; i++) {
// receives
final receiveDerivation = receiveDerivations[addressesP2SH[i]];
// if a match exists it will not be null
if (receiveDerivation != null) {
final p2wpkh = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["pubKey"] as String)),
network: _network,
overridePrefix: _network.bech32!)
.data;
final redeemScript = p2wpkh.output;
final data =
P2SH(data: PaymentData(redeem: p2wpkh), network: _network).data;
for (String tx in addressTxid[addressesP2SH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
receiveDerivation["wif"] as String,
network: _network,
),
"redeemScript": redeemScript,
};
}
} else {
// if its not a receive, check change
final changeDerivation = changeDerivations[addressesP2SH[i]];
// if a match exists it will not be null
if (changeDerivation != null) {
case DerivePathType.bip49:
final p2wpkh = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
changeDerivation["pubKey"] as String)),
network: _network,
overridePrefix: _network.bech32!)
.data;
final redeemScript = p2wpkh.output;
final data =
P2SH(data: PaymentData(redeem: p2wpkh), network: _network)
.data;
for (String tx in addressTxid[addressesP2SH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: _network,
),
"redeemScript": redeemScript,
};
}
}
}
}
}
// p2wpkh / bip84
final p2wpkhLength = addressesP2WPKH.length;
if (p2wpkhLength > 0) {
final receiveDerivations = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bip84,
);
final changeDerivations = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bip84,
);
for (int i = 0; i < p2wpkhLength; i++) {
// receives
final receiveDerivation = receiveDerivations[addressesP2WPKH[i]];
// if a match exists it will not be null
if (receiveDerivation != null) {
final data = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["pubKey"] as String)),
network: _network,
overridePrefix: _network.bech32!)
.data;
for (String tx in addressTxid[addressesP2WPKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
receiveDerivation["wif"] as String,
network: _network,
data: PaymentData(
pubkey: Format.stringToUint8List(pubKey),
),
};
}
} else {
// if its not a receive, check change
final changeDerivation = changeDerivations[addressesP2WPKH[i]];
// if a match exists it will not be null
if (changeDerivation != null) {
final data = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
changeDerivation["pubKey"] as String)),
network: _network,
overridePrefix: _network.bech32!)
.data;
network: _network,
overridePrefix: _network.bech32!,
).data;
redeemScript = p2wpkh.output;
data = P2SH(
data: PaymentData(redeem: p2wpkh),
network: _network,
).data;
break;
for (String tx in addressTxid[addressesP2WPKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: _network,
),
};
}
}
case DerivePathType.bip84:
data = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(pubKey),
),
network: _network,
overridePrefix: _network.bech32!,
).data;
redeemScript = null;
break;
default:
throw Exception("DerivePathType unsupported");
}
final keyPair = ECPair.fromWIF(
wif,
network: _network,
);
sd.redeemScript = redeemScript;
sd.output = data.output;
sd.keyPair = keyPair;
}
}
return results;
return signingData;
} catch (e, s) {
Logging.instance
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
@ -2848,8 +2726,7 @@ class LitecoinWallet extends CoinServiceAPI
/// Builds and signs a transaction
Future<Map<String, dynamic>> buildTransaction({
required List<isar_models.UTXO> utxosToUse,
required Map<String, dynamic> utxoSigningData,
required List<SigningData> utxoSigningData,
required List<String> recipients,
required List<int> satoshiAmounts,
}) async {
@ -2860,10 +2737,15 @@ class LitecoinWallet extends CoinServiceAPI
txb.setVersion(1);
// Add transaction inputs
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
txb.addInput(txid, utxosToUse[i].vout, null,
utxoSigningData[txid]["output"] as Uint8List, _network.bech32!);
for (var i = 0; i < utxoSigningData.length; i++) {
final txid = utxoSigningData[i].utxo.txid;
txb.addInput(
txid,
utxoSigningData[i].utxo.vout,
null,
utxoSigningData[i].output!,
_network.bech32!,
);
}
// Add transaction output
@ -2873,14 +2755,14 @@ class LitecoinWallet extends CoinServiceAPI
try {
// Sign the transaction accordingly
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
for (var i = 0; i < utxoSigningData.length; i++) {
txb.sign(
vin: i,
keyPair: utxoSigningData[txid]["keyPair"] as ECPair,
witnessValue: utxosToUse[i].value,
redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?,
overridePrefix: _network.bech32!);
vin: i,
keyPair: utxoSigningData[i].keyPair!,
witnessValue: utxoSigningData[i].utxo.value,
redeemScript: utxoSigningData[i].redeemScript,
overridePrefix: _network.bech32!,
);
}
} catch (e, s) {
Logging.instance.log("Caught exception while signing transaction: $e\n$s",

View file

@ -17,6 +17,7 @@ import 'package:stackwallet/electrumx_rpc/electrumx.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/signing_data.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';
@ -2331,7 +2332,6 @@ class NamecoinWallet extends CoinServiceAPI
.log("Attempting to send all $coin", level: LogLevel.Info);
final int vSizeForOneOutput = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
@ -2349,7 +2349,6 @@ class NamecoinWallet extends CoinServiceAPI
final int amount = satoshiAmountToSend - feeForOneOutput;
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: [amount],
@ -2360,19 +2359,17 @@ class NamecoinWallet extends CoinServiceAPI
"recipientAmt": amount,
"fee": feeForOneOutput,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
final int vSizeForOneOutput = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
final int vSizeForTwoOutPuts = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [
recipientAddress,
@ -2438,7 +2435,6 @@ class NamecoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2466,7 +2462,6 @@ class NamecoinWallet extends CoinServiceAPI
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
level: LogLevel.Info);
txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2479,7 +2474,7 @@ class NamecoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": feeBeingPaid,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
} else {
@ -2496,7 +2491,6 @@ class NamecoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2507,7 +2501,7 @@ class NamecoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": satoshisBeingUsed - satoshiAmountToSend,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
@ -2526,7 +2520,6 @@ class NamecoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2537,7 +2530,7 @@ class NamecoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": satoshisBeingUsed - satoshiAmountToSend,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
@ -2556,7 +2549,6 @@ class NamecoinWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2567,7 +2559,7 @@ class NamecoinWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": feeForOneOutput,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
} else {
@ -2593,248 +2585,129 @@ class NamecoinWallet extends CoinServiceAPI
}
}
Future<Map<String, dynamic>> fetchBuildTxData(
Future<List<SigningData>> fetchBuildTxData(
List<isar_models.UTXO> utxosToUse,
) async {
// return data
Map<String, dynamic> results = {};
Map<String, List<String>> addressTxid = {};
// addresses to check
List<String> addressesP2PKH = [];
List<String> addressesP2SH = [];
List<String> addressesP2WPKH = [];
Logging.instance.log("utxos: $utxosToUse", level: LogLevel.Info);
List<SigningData> signingData = [];
try {
// Populating the addresses to check
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: txid,
coin: coin,
);
Logging.instance.log("tx: ${json.encode(tx)}",
level: LogLevel.Info, printFullLength: true);
for (final output in tx["vout"] as List) {
final n = output["n"];
if (n != null && n == utxosToUse[i].vout) {
final address = output["scriptPubKey"]["address"] as String;
if (!addressTxid.containsKey(address)) {
addressTxid[address] = <String>[];
}
(addressTxid[address] as List).add(txid);
switch (addressType(address: address)) {
case DerivePathType.bip44:
addressesP2PKH.add(address);
break;
case DerivePathType.bip49:
addressesP2SH.add(address);
break;
case DerivePathType.bip84:
addressesP2WPKH.add(address);
break;
default:
throw Exception("DerivePathType unsupported");
if (utxosToUse[i].address == null) {
final txid = utxosToUse[i].txid;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: txid,
coin: coin,
);
for (final output in tx["vout"] as List) {
final n = output["n"];
if (n != null && n == utxosToUse[i].vout) {
utxosToUse[i] = utxosToUse[i].copyWith(
address: output["scriptPubKey"]?["addresses"]?[0] as String? ??
output["scriptPubKey"]["address"] as String,
);
}
}
}
final derivePathType = addressType(address: utxosToUse[i].address!);
signingData.add(
SigningData(
derivePathType: derivePathType,
utxo: utxosToUse[i],
),
);
}
// p2pkh / bip44
final p2pkhLength = addressesP2PKH.length;
if (p2pkhLength > 0) {
final receiveDerivations = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bip44,
);
final changeDerivations = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bip44,
);
for (int i = 0; i < p2pkhLength; i++) {
// receives
final receiveDerivation = receiveDerivations[addressesP2PKH[i]];
// if a match exists it will not be null
if (receiveDerivation != null) {
final data = P2PKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["pubKey"] as String)),
network: _network,
).data;
Map<DerivePathType, Map<String, dynamic>> receiveDerivations = {};
Map<DerivePathType, Map<String, dynamic>> changeDerivations = {};
for (String tx in addressTxid[addressesP2PKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
receiveDerivation["wif"] as String,
network: _network,
),
};
}
} else {
// if its not a receive, check change
final changeDerivation = changeDerivations[addressesP2PKH[i]];
// if a match exists it will not be null
if (changeDerivation != null) {
final data = P2PKH(
for (final sd in signingData) {
String? pubKey;
String? wif;
// fetch receiving derivations if null
receiveDerivations[sd.derivePathType] ??= await _fetchDerivations(
chain: 0,
derivePathType: sd.derivePathType,
);
final receiveDerivation =
receiveDerivations[sd.derivePathType]![sd.utxo.address!];
if (receiveDerivation != null) {
pubKey = receiveDerivation["pubKey"] as String;
wif = receiveDerivation["wif"] as String;
} else {
// fetch change derivations if null
changeDerivations[sd.derivePathType] ??= await _fetchDerivations(
chain: 1,
derivePathType: sd.derivePathType,
);
final changeDerivation =
changeDerivations[sd.derivePathType]![sd.utxo.address!];
if (changeDerivation != null) {
pubKey = changeDerivation["pubKey"] as String;
wif = changeDerivation["wif"] as String;
}
}
if (wif != null && pubKey != null) {
final PaymentData data;
final Uint8List? redeemScript;
switch (sd.derivePathType) {
case DerivePathType.bip44:
data = P2PKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
changeDerivation["pubKey"] as String)),
pubkey: Format.stringToUint8List(pubKey),
),
network: _network,
).data;
redeemScript = null;
break;
for (String tx in addressTxid[addressesP2PKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: _network,
),
};
}
}
}
}
}
// p2sh / bip49
final p2shLength = addressesP2SH.length;
if (p2shLength > 0) {
final receiveDerivations = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bip49,
);
final changeDerivations = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bip49,
);
for (int i = 0; i < p2shLength; i++) {
// receives
final receiveDerivation = receiveDerivations[addressesP2SH[i]];
// if a match exists it will not be null
if (receiveDerivation != null) {
final p2wpkh = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["pubKey"] as String)),
network: _network,
overridePrefix: namecoin.bech32!)
.data;
final redeemScript = p2wpkh.output;
final data =
P2SH(data: PaymentData(redeem: p2wpkh), network: _network).data;
for (String tx in addressTxid[addressesP2SH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
receiveDerivation["wif"] as String,
network: _network,
),
"redeemScript": redeemScript,
};
}
} else {
// if its not a receive, check change
final changeDerivation = changeDerivations[addressesP2SH[i]];
// if a match exists it will not be null
if (changeDerivation != null) {
case DerivePathType.bip49:
final p2wpkh = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
changeDerivation["pubKey"] as String)),
network: _network,
overridePrefix: namecoin.bech32!)
.data;
final redeemScript = p2wpkh.output;
final data =
P2SH(data: PaymentData(redeem: p2wpkh), network: _network)
.data;
for (String tx in addressTxid[addressesP2SH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: _network,
),
"redeemScript": redeemScript,
};
}
}
}
}
}
// p2wpkh / bip84
final p2wpkhLength = addressesP2WPKH.length;
if (p2wpkhLength > 0) {
final receiveDerivations = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bip84,
);
final changeDerivations = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bip84,
);
for (int i = 0; i < p2wpkhLength; i++) {
// receives
final receiveDerivation = receiveDerivations[addressesP2WPKH[i]];
// if a match exists it will not be null
if (receiveDerivation != null) {
final data = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["pubKey"] as String)),
network: _network,
overridePrefix: namecoin.bech32!)
.data;
for (String tx in addressTxid[addressesP2WPKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
receiveDerivation["wif"] as String,
network: _network,
data: PaymentData(
pubkey: Format.stringToUint8List(pubKey),
),
};
}
} else {
// if its not a receive, check change
final changeDerivation = changeDerivations[addressesP2WPKH[i]];
// if a match exists it will not be null
if (changeDerivation != null) {
final data = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
changeDerivation["pubKey"] as String)),
network: _network,
overridePrefix: namecoin.bech32!)
network: _network,
overridePrefix: _network.bech32!,
).data;
redeemScript = p2wpkh.output;
data = P2SH(data: PaymentData(redeem: p2wpkh), network: _network)
.data;
break;
for (String tx in addressTxid[addressesP2WPKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: _network,
),
};
}
}
case DerivePathType.bip84:
data = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(pubKey),
),
network: _network,
overridePrefix: _network.bech32!,
).data;
redeemScript = null;
break;
default:
throw Exception("DerivePathType unsupported");
}
final keyPair = ECPair.fromWIF(
wif,
network: _network,
);
sd.redeemScript = redeemScript;
sd.output = data.output;
sd.keyPair = keyPair;
}
}
return results;
return signingData;
} catch (e, s) {
Logging.instance
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
@ -2844,8 +2717,7 @@ class NamecoinWallet extends CoinServiceAPI
/// Builds and signs a transaction
Future<Map<String, dynamic>> buildTransaction({
required List<isar_models.UTXO> utxosToUse,
required Map<String, dynamic> utxoSigningData,
required List<SigningData> utxoSigningData,
required List<String> recipients,
required List<int> satoshiAmounts,
}) async {
@ -2856,10 +2728,15 @@ class NamecoinWallet extends CoinServiceAPI
txb.setVersion(2);
// Add transaction inputs
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
txb.addInput(txid, utxosToUse[i].vout, null,
utxoSigningData[txid]["output"] as Uint8List, namecoin.bech32!);
for (var i = 0; i < utxoSigningData.length; i++) {
final txid = utxoSigningData[i].utxo.txid;
txb.addInput(
txid,
utxoSigningData[i].utxo.vout,
null,
utxoSigningData[i].output!,
_network.bech32!,
);
}
// Add transaction output
@ -2869,14 +2746,15 @@ class NamecoinWallet extends CoinServiceAPI
try {
// Sign the transaction accordingly
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
txb.sign(
vin: i,
keyPair: utxoSigningData[txid]["keyPair"] as ECPair,
witnessValue: utxosToUse[i].value,
redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?,
overridePrefix: namecoin.bech32!);
for (var i = 0; i < utxoSigningData.length; i++) {
final txid = utxoSigningData[i].utxo.txid;
txb.addInput(
txid,
utxoSigningData[i].utxo.vout,
null,
utxoSigningData[i].output!,
_network.bech32!,
);
}
} catch (e, s) {
Logging.instance.log("Caught exception while signing transaction: $e\n$s",

View file

@ -17,6 +17,7 @@ import 'package:stackwallet/electrumx_rpc/electrumx.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/signing_data.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';
@ -2492,7 +2493,6 @@ class ParticlWallet extends CoinServiceAPI
.log("Attempting to send all $coin", level: LogLevel.Info);
final int vSizeForOneOutput = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
@ -2510,7 +2510,6 @@ class ParticlWallet extends CoinServiceAPI
final int amount = satoshiAmountToSend - feeForOneOutput;
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: [amount],
@ -2521,19 +2520,17 @@ class ParticlWallet extends CoinServiceAPI
"recipientAmt": amount,
"fee": feeForOneOutput,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
final int vSizeForOneOutput = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [recipientAddress],
satoshiAmounts: [satoshisBeingUsed - 1],
))["vSize"] as int;
final int vSizeForTwoOutPuts = (await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: [
recipientAddress,
@ -2599,7 +2596,6 @@ class ParticlWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2627,7 +2623,6 @@ class ParticlWallet extends CoinServiceAPI
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
level: LogLevel.Info);
txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2640,7 +2635,7 @@ class ParticlWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": feeBeingPaid,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
} else {
@ -2657,7 +2652,6 @@ class ParticlWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2668,7 +2662,7 @@ class ParticlWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": satoshisBeingUsed - satoshiAmountToSend,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
@ -2687,7 +2681,6 @@ class ParticlWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2698,7 +2691,7 @@ class ParticlWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": satoshisBeingUsed - satoshiAmountToSend,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
}
@ -2717,7 +2710,6 @@ class ParticlWallet extends CoinServiceAPI
Logging.instance
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
dynamic txn = await buildTransaction(
utxosToUse: utxoObjectsToUse,
utxoSigningData: utxoSigningData,
recipients: recipientsArray,
satoshiAmounts: recipientsAmtArray,
@ -2728,7 +2720,7 @@ class ParticlWallet extends CoinServiceAPI
"recipientAmt": recipientsAmtArray[0],
"fee": feeForOneOutput,
"vSize": txn["vSize"],
"usedUTXOs": utxoObjectsToUse,
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
};
return transactionObject;
} else {
@ -2754,168 +2746,115 @@ class ParticlWallet extends CoinServiceAPI
}
}
Future<Map<String, dynamic>> fetchBuildTxData(
Future<List<SigningData>> fetchBuildTxData(
List<isar_models.UTXO> utxosToUse,
) async {
// return data
Map<String, dynamic> results = {};
Map<String, List<String>> addressTxid = {};
// addresses to check
List<String> addressesP2PKH = [];
List<String> addressesP2WPKH = [];
List<SigningData> signingData = [];
try {
// Populating the addresses to check
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: txid,
coin: coin,
);
for (final output in tx["vout"] as List) {
final n = output["n"];
if (n != null && n == utxosToUse[i].vout) {
final address = output["scriptPubKey"]["addresses"][0] as String;
if (!addressTxid.containsKey(address)) {
addressTxid[address] = <String>[];
}
(addressTxid[address] as List).add(txid);
switch (addressType(address: address)) {
case DerivePathType.bip44:
addressesP2PKH.add(address);
break;
case DerivePathType.bip84:
addressesP2WPKH.add(address);
break;
default:
throw Exception(
"DerivePathType ${addressType(address: address)} not supported");
if (utxosToUse[i].address == null) {
final txid = utxosToUse[i].txid;
final tx = await _cachedElectrumXClient.getTransaction(
txHash: txid,
coin: coin,
);
for (final output in tx["vout"] as List) {
final n = output["n"];
if (n != null && n == utxosToUse[i].vout) {
utxosToUse[i] = utxosToUse[i].copyWith(
address: output["scriptPubKey"]?["addresses"]?[0] as String? ??
output["scriptPubKey"]["address"] as String,
);
}
}
}
final derivePathType = addressType(address: utxosToUse[i].address!);
signingData.add(
SigningData(
derivePathType: derivePathType,
utxo: utxosToUse[i],
),
);
}
// p2pkh / bip44
final p2pkhLength = addressesP2PKH.length;
if (p2pkhLength > 0) {
final receiveDerivations = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bip44,
);
final changeDerivations = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bip44,
);
for (int i = 0; i < p2pkhLength; i++) {
// receives
final receiveDerivation = receiveDerivations[addressesP2PKH[i]];
// if a match exists it will not be null
if (receiveDerivation != null) {
final data = P2PKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["pubKey"] as String)),
network: _network,
).data;
Map<DerivePathType, Map<String, dynamic>> receiveDerivations = {};
Map<DerivePathType, Map<String, dynamic>> changeDerivations = {};
for (String tx in addressTxid[addressesP2PKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
receiveDerivation["wif"] as String,
network: _network,
),
};
}
} else {
// if its not a receive, check change
final changeDerivation = changeDerivations[addressesP2PKH[i]];
// if a match exists it will not be null
if (changeDerivation != null) {
final data = P2PKH(
for (final sd in signingData) {
String? pubKey;
String? wif;
// fetch receiving derivations if null
receiveDerivations[sd.derivePathType] ??= await _fetchDerivations(
chain: 0,
derivePathType: sd.derivePathType,
);
final receiveDerivation =
receiveDerivations[sd.derivePathType]![sd.utxo.address!];
if (receiveDerivation != null) {
pubKey = receiveDerivation["pubKey"] as String;
wif = receiveDerivation["wif"] as String;
} else {
// fetch change derivations if null
changeDerivations[sd.derivePathType] ??= await _fetchDerivations(
chain: 1,
derivePathType: sd.derivePathType,
);
final changeDerivation =
changeDerivations[sd.derivePathType]![sd.utxo.address!];
if (changeDerivation != null) {
pubKey = changeDerivation["pubKey"] as String;
wif = changeDerivation["wif"] as String;
}
}
if (wif != null && pubKey != null) {
final PaymentData data;
final Uint8List? redeemScript;
switch (sd.derivePathType) {
case DerivePathType.bip44:
data = P2PKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
changeDerivation["pubKey"] as String)),
pubkey: Format.stringToUint8List(pubKey),
),
network: _network,
).data;
redeemScript = null;
break;
for (String tx in addressTxid[addressesP2PKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: _network,
),
};
}
}
}
}
}
// p2wpkh / bip84
final p2wpkhLength = addressesP2WPKH.length;
if (p2wpkhLength > 0) {
final receiveDerivations = await _fetchDerivations(
chain: 0,
derivePathType: DerivePathType.bip84,
);
final changeDerivations = await _fetchDerivations(
chain: 1,
derivePathType: DerivePathType.bip84,
);
for (int i = 0; i < p2wpkhLength; i++) {
// receives
final receiveDerivation = receiveDerivations[addressesP2WPKH[i]];
// if a match exists it will not be null
if (receiveDerivation != null) {
final data = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
receiveDerivation["pubKey"] as String)),
network: _network,
).data;
for (String tx in addressTxid[addressesP2WPKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
receiveDerivation["wif"] as String,
network: _network,
),
};
}
} else {
// if its not a receive, check change
final changeDerivation = changeDerivations[addressesP2WPKH[i]];
// if a match exists it will not be null
if (changeDerivation != null) {
final data = P2WPKH(
case DerivePathType.bip84:
data = P2WPKH(
data: PaymentData(
pubkey: Format.stringToUint8List(
changeDerivation["pubKey"] as String)),
pubkey: Format.stringToUint8List(pubKey),
),
network: _network,
).data;
redeemScript = null;
break;
for (String tx in addressTxid[addressesP2WPKH[i]]!) {
results[tx] = {
"output": data.output,
"keyPair": ECPair.fromWIF(
changeDerivation["wif"] as String,
network: _network,
),
};
}
}
default:
throw Exception("DerivePathType unsupported");
}
final keyPair = ECPair.fromWIF(
wif,
network: _network,
);
sd.redeemScript = redeemScript;
sd.output = data.output;
sd.keyPair = keyPair;
}
}
Logging.instance.log("FETCHED TX BUILD DATA IS -----$results",
level: LogLevel.Info, printFullLength: true);
return results;
return signingData;
} catch (e, s) {
Logging.instance
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
@ -2925,8 +2864,7 @@ class ParticlWallet extends CoinServiceAPI
/// Builds and signs a transaction
Future<Map<String, dynamic>> buildTransaction({
required List<isar_models.UTXO> utxosToUse,
required Map<String, dynamic> utxoSigningData,
required List<SigningData> utxoSigningData,
required List<String> recipients,
required List<int> satoshiAmounts,
}) async {
@ -2940,11 +2878,15 @@ class ParticlWallet extends CoinServiceAPI
txb.setVersion(160);
// Add transaction inputs
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
txb.addInput(txid, utxosToUse[i].vout, null,
utxoSigningData[txid]["output"] as Uint8List, '');
for (var i = 0; i < utxoSigningData.length; i++) {
final txid = utxoSigningData[i].utxo.txid;
txb.addInput(
txid,
utxoSigningData[i].utxo.vout,
null,
utxoSigningData[i].output!,
'',
);
}
// Add transaction output
@ -2954,13 +2896,13 @@ class ParticlWallet extends CoinServiceAPI
try {
// Sign the transaction accordingly
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
for (var i = 0; i < utxoSigningData.length; i++) {
txb.sign(
vin: i,
keyPair: utxoSigningData[txid]["keyPair"] as ECPair,
witnessValue: utxosToUse[i].value,
redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?);
vin: i,
keyPair: utxoSigningData[i].keyPair!,
witnessValue: utxoSigningData[i].utxo.value,
redeemScript: utxoSigningData[i].redeemScript,
);
}
} catch (e, s) {
Logging.instance.log("Caught exception while signing transaction: $e\n$s",