mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-11 05:04:35 +00:00
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:
parent
260771061c
commit
c04723840f
6 changed files with 676 additions and 1094 deletions
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue