mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-26 20:26:02 +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/balance.dart';
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
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/paymint/fee_object_model.dart';
|
||||||
|
import 'package:stackwallet/models/signing_data.dart';
|
||||||
import 'package:stackwallet/services/coins/coin_service.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/node_connection_status_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_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);
|
.log("Attempting to send all $coin", level: LogLevel.Info);
|
||||||
|
|
||||||
final int vSizeForOneOutput = (await buildTransaction(
|
final int vSizeForOneOutput = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [recipientAddress],
|
recipients: [recipientAddress],
|
||||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||||
|
@ -2402,7 +2402,6 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
|
|
||||||
final int amount = satoshiAmountToSend - feeForOneOutput;
|
final int amount = satoshiAmountToSend - feeForOneOutput;
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: [amount],
|
satoshiAmounts: [amount],
|
||||||
|
@ -2413,7 +2412,7 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": amount,
|
"recipientAmt": amount,
|
||||||
"fee": feeForOneOutput,
|
"fee": feeForOneOutput,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
@ -2421,7 +2420,6 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
final int vSizeForOneOutput;
|
final int vSizeForOneOutput;
|
||||||
try {
|
try {
|
||||||
vSizeForOneOutput = (await buildTransaction(
|
vSizeForOneOutput = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [recipientAddress],
|
recipients: [recipientAddress],
|
||||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||||
|
@ -2434,7 +2432,6 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
final int vSizeForTwoOutPuts;
|
final int vSizeForTwoOutPuts;
|
||||||
try {
|
try {
|
||||||
vSizeForTwoOutPuts = (await buildTransaction(
|
vSizeForTwoOutPuts = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [
|
recipients: [
|
||||||
recipientAddress,
|
recipientAddress,
|
||||||
|
@ -2505,7 +2502,6 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
|
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2533,7 +2529,6 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
|
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
txn = await buildTransaction(
|
txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2546,7 +2541,7 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": feeBeingPaid,
|
"fee": feeBeingPaid,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2563,7 +2558,6 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2574,7 +2568,7 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
@ -2593,7 +2587,6 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2604,7 +2597,7 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
@ -2623,7 +2616,6 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2634,7 +2626,7 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": feeForOneOutput,
|
"fee": feeForOneOutput,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2660,241 +2652,129 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> fetchBuildTxData(
|
Future<List<SigningData>> fetchBuildTxData(
|
||||||
List<isar_models.UTXO> utxosToUse,
|
List<isar_models.UTXO> utxosToUse,
|
||||||
) async {
|
) async {
|
||||||
// return data
|
// return data
|
||||||
Map<String, dynamic> results = {};
|
List<SigningData> signingData = [];
|
||||||
Map<String, List<String>> addressTxid = {};
|
|
||||||
|
|
||||||
// addresses to check
|
|
||||||
List<String> addressesP2PKH = [];
|
|
||||||
List<String> addressesP2SH = [];
|
|
||||||
List<String> addressesP2WPKH = [];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Populating the addresses to check
|
// Populating the addresses to check
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxosToUse.length; i++) {
|
||||||
|
if (utxosToUse[i].address == null) {
|
||||||
final txid = utxosToUse[i].txid;
|
final txid = utxosToUse[i].txid;
|
||||||
final tx = await _cachedElectrumXClient.getTransaction(
|
final tx = await _cachedElectrumXClient.getTransaction(
|
||||||
txHash: txid,
|
txHash: txid,
|
||||||
coin: coin,
|
coin: coin,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (final output in tx["vout"] as List) {
|
for (final output in tx["vout"] as List) {
|
||||||
final n = output["n"];
|
final n = output["n"];
|
||||||
if (n != null && n == utxosToUse[i].vout) {
|
if (n != null && n == utxosToUse[i].vout) {
|
||||||
final address = output["scriptPubKey"]["address"] as String;
|
utxosToUse[i] = utxosToUse[i].copyWith(
|
||||||
if (!addressTxid.containsKey(address)) {
|
address: output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||||
addressTxid[address] = <String>[];
|
output["scriptPubKey"]["address"] as String,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
(addressTxid[address] as List).add(txid);
|
}
|
||||||
switch (addressType(address: address)) {
|
}
|
||||||
|
|
||||||
|
final derivePathType = addressType(address: utxosToUse[i].address!);
|
||||||
|
|
||||||
|
signingData.add(
|
||||||
|
SigningData(
|
||||||
|
derivePathType: derivePathType,
|
||||||
|
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] ??= 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:
|
case DerivePathType.bip44:
|
||||||
addressesP2PKH.add(address);
|
data = P2PKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey: Format.stringToUint8List(pubKey),
|
||||||
|
),
|
||||||
|
network: _network,
|
||||||
|
).data;
|
||||||
|
redeemScript = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DerivePathType.bip49:
|
case DerivePathType.bip49:
|
||||||
addressesP2SH.add(address);
|
final p2wpkh = P2WPKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey: Format.stringToUint8List(pubKey),
|
||||||
|
),
|
||||||
|
network: _network,
|
||||||
|
).data;
|
||||||
|
redeemScript = p2wpkh.output;
|
||||||
|
data = P2SH(
|
||||||
|
data: PaymentData(redeem: p2wpkh),
|
||||||
|
network: _network,
|
||||||
|
).data;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DerivePathType.bip84:
|
case DerivePathType.bip84:
|
||||||
addressesP2WPKH.add(address);
|
data = P2WPKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey: Format.stringToUint8List(pubKey),
|
||||||
|
),
|
||||||
|
network: _network,
|
||||||
|
).data;
|
||||||
|
redeemScript = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw Exception("DerivePathType unsupported");
|
throw Exception("DerivePathType unsupported");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// p2pkh / bip44
|
final keyPair = ECPair.fromWIF(
|
||||||
final p2pkhLength = addressesP2PKH.length;
|
wif,
|
||||||
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,
|
network: _network,
|
||||||
).data;
|
|
||||||
|
|
||||||
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(
|
|
||||||
data: PaymentData(
|
|
||||||
pubkey: Format.stringToUint8List(
|
|
||||||
changeDerivation["pubKey"] as String)),
|
|
||||||
network: _network,
|
|
||||||
).data;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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++) {
|
sd.redeemScript = redeemScript;
|
||||||
// receives
|
sd.output = data.output;
|
||||||
final receiveDerivation = receiveDerivations[addressesP2WPKH[i]];
|
sd.keyPair = keyPair;
|
||||||
// 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)),
|
|
||||||
network: _network,
|
|
||||||
).data;
|
|
||||||
|
|
||||||
for (String tx in addressTxid[addressesP2WPKH[i]]!) {
|
|
||||||
results[tx] = {
|
|
||||||
"output": data.output,
|
|
||||||
"keyPair": ECPair.fromWIF(
|
|
||||||
changeDerivation["wif"] as String,
|
|
||||||
network: _network,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return signingData;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
|
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
|
||||||
|
@ -2904,8 +2784,7 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
|
|
||||||
/// Builds and signs a transaction
|
/// Builds and signs a transaction
|
||||||
Future<Map<String, dynamic>> buildTransaction({
|
Future<Map<String, dynamic>> buildTransaction({
|
||||||
required List<isar_models.UTXO> utxosToUse,
|
required List<SigningData> utxoSigningData,
|
||||||
required Map<String, dynamic> utxoSigningData,
|
|
||||||
required List<String> recipients,
|
required List<String> recipients,
|
||||||
required List<int> satoshiAmounts,
|
required List<int> satoshiAmounts,
|
||||||
}) async {
|
}) async {
|
||||||
|
@ -2916,10 +2795,14 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
txb.setVersion(1);
|
txb.setVersion(1);
|
||||||
|
|
||||||
// Add transaction inputs
|
// Add transaction inputs
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||||
final txid = utxosToUse[i].txid;
|
final txid = utxoSigningData[i].utxo.txid;
|
||||||
txb.addInput(txid, utxosToUse[i].vout, null,
|
txb.addInput(
|
||||||
utxoSigningData[txid]["output"] as Uint8List);
|
txid,
|
||||||
|
utxoSigningData[i].utxo.vout,
|
||||||
|
null,
|
||||||
|
utxoSigningData[i].output!,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add transaction output
|
// Add transaction output
|
||||||
|
@ -2929,13 +2812,12 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Sign the transaction accordingly
|
// Sign the transaction accordingly
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||||
final txid = utxosToUse[i].txid;
|
|
||||||
txb.sign(
|
txb.sign(
|
||||||
vin: i,
|
vin: i,
|
||||||
keyPair: utxoSigningData[txid]["keyPair"] as ECPair,
|
keyPair: utxoSigningData[i].keyPair!,
|
||||||
witnessValue: utxosToUse[i].value,
|
witnessValue: utxoSigningData[i].utxo.value,
|
||||||
redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?,
|
redeemScript: utxoSigningData[i].redeemScript,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e, s) {
|
} 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/balance.dart';
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
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/paymint/fee_object_model.dart';
|
||||||
|
import 'package:stackwallet/models/signing_data.dart';
|
||||||
import 'package:stackwallet/services/coins/coin_service.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/node_connection_status_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_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);
|
.log("Attempting to send all $coin", level: LogLevel.Info);
|
||||||
|
|
||||||
final int vSizeForOneOutput = (await buildTransaction(
|
final int vSizeForOneOutput = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [recipientAddress],
|
recipients: [recipientAddress],
|
||||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||||
|
@ -2167,7 +2167,6 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
|
|
||||||
final int amount = satoshiAmountToSend - feeForOneOutput;
|
final int amount = satoshiAmountToSend - feeForOneOutput;
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: [amount],
|
satoshiAmounts: [amount],
|
||||||
|
@ -2178,19 +2177,17 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": amount,
|
"recipientAmt": amount,
|
||||||
"fee": feeForOneOutput,
|
"fee": feeForOneOutput,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int vSizeForOneOutput = (await buildTransaction(
|
final int vSizeForOneOutput = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [recipientAddress],
|
recipients: [recipientAddress],
|
||||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||||
))["vSize"] as int;
|
))["vSize"] as int;
|
||||||
final int vSizeForTwoOutPuts = (await buildTransaction(
|
final int vSizeForTwoOutPuts = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [
|
recipients: [
|
||||||
recipientAddress,
|
recipientAddress,
|
||||||
|
@ -2270,7 +2267,6 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
|
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2298,7 +2294,6 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
|
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
txn = await buildTransaction(
|
txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2311,7 +2306,7 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": feeBeingPaid,
|
"fee": feeBeingPaid,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2328,7 +2323,6 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2339,7 +2333,7 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
@ -2358,7 +2352,6 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2369,7 +2362,7 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
@ -2388,7 +2381,6 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2399,7 +2391,7 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": feeForOneOutput,
|
"fee": feeForOneOutput,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2425,103 +2417,105 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> fetchBuildTxData(
|
Future<List<SigningData>> fetchBuildTxData(
|
||||||
List<isar_models.UTXO> utxosToUse,
|
List<isar_models.UTXO> utxosToUse,
|
||||||
) async {
|
) async {
|
||||||
// return data
|
// return data
|
||||||
Map<String, dynamic> results = {};
|
List<SigningData> signingData = [];
|
||||||
Map<String, List<String>> addressTxid = {};
|
|
||||||
|
|
||||||
// addresses to check
|
|
||||||
List<String> addressesP2PKH = [];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Populating the addresses to check
|
// Populating the addresses to check
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxosToUse.length; i++) {
|
||||||
|
if (utxosToUse[i].address == null) {
|
||||||
final txid = utxosToUse[i].txid;
|
final txid = utxosToUse[i].txid;
|
||||||
final tx = await _cachedElectrumXClient.getTransaction(
|
final tx = await _cachedElectrumXClient.getTransaction(
|
||||||
txHash: txid,
|
txHash: txid,
|
||||||
coin: coin,
|
coin: coin,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (final output in tx["vout"] as List) {
|
for (final output in tx["vout"] as List) {
|
||||||
final n = output["n"];
|
final n = output["n"];
|
||||||
if (n != null && n == utxosToUse[i].vout) {
|
if (n != null && n == utxosToUse[i].vout) {
|
||||||
final address = output["scriptPubKey"]["addresses"][0] as String;
|
utxosToUse[i] = utxosToUse[i].copyWith(
|
||||||
if (!addressTxid.containsKey(address)) {
|
address: output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||||
addressTxid[address] = <String>[];
|
output["scriptPubKey"]["address"] as String,
|
||||||
}
|
);
|
||||||
(addressTxid[address] as List).add(txid);
|
|
||||||
switch (addressType(address: address)) {
|
|
||||||
case DerivePathType.bip44:
|
|
||||||
addressesP2PKH.add(address);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw Exception("Unsupported DerivePathType");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// p2pkh / bip44
|
final derivePathType = addressType(address: utxosToUse[i].address!);
|
||||||
final p2pkhLength = addressesP2PKH.length;
|
|
||||||
if (p2pkhLength > 0) {
|
signingData.add(
|
||||||
final receiveDerivations = await _fetchDerivations(
|
SigningData(
|
||||||
|
derivePathType: derivePathType,
|
||||||
|
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] ??= await _fetchDerivations(
|
||||||
chain: 0,
|
chain: 0,
|
||||||
derivePathType: DerivePathType.bip44,
|
derivePathType: sd.derivePathType,
|
||||||
);
|
);
|
||||||
final changeDerivations = await _fetchDerivations(
|
final receiveDerivation =
|
||||||
chain: 1,
|
receiveDerivations[sd.derivePathType]![sd.utxo.address!];
|
||||||
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) {
|
if (receiveDerivation != null) {
|
||||||
final data = P2PKH(
|
pubKey = receiveDerivation["pubKey"] as String;
|
||||||
data: PaymentData(
|
wif = receiveDerivation["wif"] as String;
|
||||||
pubkey: Format.stringToUint8List(
|
|
||||||
receiveDerivation["pubKey"] as String)),
|
|
||||||
network: network,
|
|
||||||
).data;
|
|
||||||
|
|
||||||
for (String tx in addressTxid[addressesP2PKH[i]]!) {
|
|
||||||
results[tx] = {
|
|
||||||
"output": data.output,
|
|
||||||
"keyPair": ECPair.fromWIF(
|
|
||||||
receiveDerivation["wif"] as String,
|
|
||||||
network: network,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// if its not a receive, check change
|
// fetch change derivations if null
|
||||||
final changeDerivation = changeDerivations[addressesP2PKH[i]];
|
changeDerivations[sd.derivePathType] ??= await _fetchDerivations(
|
||||||
// if a match exists it will not be null
|
chain: 1,
|
||||||
|
derivePathType: sd.derivePathType,
|
||||||
|
);
|
||||||
|
final changeDerivation =
|
||||||
|
changeDerivations[sd.derivePathType]![sd.utxo.address!];
|
||||||
if (changeDerivation != null) {
|
if (changeDerivation != null) {
|
||||||
final data = P2PKH(
|
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(
|
data: PaymentData(
|
||||||
pubkey: Format.stringToUint8List(
|
pubkey: Format.stringToUint8List(pubKey),
|
||||||
changeDerivation["pubKey"] as String)),
|
),
|
||||||
network: network,
|
network: network,
|
||||||
).data;
|
).data;
|
||||||
|
redeemScript = null;
|
||||||
|
break;
|
||||||
|
|
||||||
for (String tx in addressTxid[addressesP2PKH[i]]!) {
|
default:
|
||||||
results[tx] = {
|
throw Exception("DerivePathType unsupported");
|
||||||
"output": data.output,
|
}
|
||||||
"keyPair": ECPair.fromWIF(
|
|
||||||
changeDerivation["wif"] as String,
|
final keyPair = ECPair.fromWIF(
|
||||||
|
wif,
|
||||||
network: network,
|
network: network,
|
||||||
),
|
);
|
||||||
};
|
|
||||||
}
|
sd.redeemScript = redeemScript;
|
||||||
}
|
sd.output = data.output;
|
||||||
}
|
sd.keyPair = keyPair;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return signingData;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
|
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
|
||||||
|
@ -2531,8 +2525,7 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
|
|
||||||
/// Builds and signs a transaction
|
/// Builds and signs a transaction
|
||||||
Future<Map<String, dynamic>> buildTransaction({
|
Future<Map<String, dynamic>> buildTransaction({
|
||||||
required List<isar_models.UTXO> utxosToUse,
|
required List<SigningData> utxoSigningData,
|
||||||
required Map<String, dynamic> utxoSigningData,
|
|
||||||
required List<String> recipients,
|
required List<String> recipients,
|
||||||
required List<int> satoshiAmounts,
|
required List<int> satoshiAmounts,
|
||||||
}) async {
|
}) async {
|
||||||
|
@ -2543,10 +2536,14 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
txb.setVersion(1);
|
txb.setVersion(1);
|
||||||
|
|
||||||
// Add transaction inputs
|
// Add transaction inputs
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||||
final txid = utxosToUse[i].txid;
|
final txid = utxoSigningData[i].utxo.txid;
|
||||||
txb.addInput(txid, utxosToUse[i].vout, null,
|
txb.addInput(
|
||||||
utxoSigningData[txid]["output"] as Uint8List);
|
txid,
|
||||||
|
utxoSigningData[i].utxo.vout,
|
||||||
|
null,
|
||||||
|
utxoSigningData[i].output!,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add transaction output
|
// Add transaction output
|
||||||
|
@ -2556,13 +2553,12 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Sign the transaction accordingly
|
// Sign the transaction accordingly
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||||
final txid = utxosToUse[i].txid;
|
|
||||||
txb.sign(
|
txb.sign(
|
||||||
vin: i,
|
vin: i,
|
||||||
keyPair: utxoSigningData[txid]["keyPair"] as ECPair,
|
keyPair: utxoSigningData[i].keyPair!,
|
||||||
witnessValue: utxosToUse[i].value,
|
witnessValue: utxoSigningData[i].utxo.value,
|
||||||
redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?,
|
redeemScript: utxoSigningData[i].redeemScript,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e, s) {
|
} 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_coin.dart';
|
||||||
import 'package:stackwallet/models/lelantus_fee_data.dart';
|
import 'package:stackwallet/models/lelantus_fee_data.dart';
|
||||||
import 'package:stackwallet/models/paymint/fee_object_model.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/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/node_connection_status_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_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/constants.dart';
|
||||||
import 'package:stackwallet/utilities/default_nodes.dart';
|
import 'package:stackwallet/utilities/default_nodes.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.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/enums/fee_rate_type_enum.dart';
|
||||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||||
import 'package:stackwallet/utilities/format.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);
|
.log("Attempting to send all $coin", level: LogLevel.Info);
|
||||||
|
|
||||||
final int vSizeForOneOutput = (await buildTransaction(
|
final int vSizeForOneOutput = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [_recipientAddress],
|
recipients: [_recipientAddress],
|
||||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||||
|
@ -1383,7 +1384,6 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
||||||
|
|
||||||
final int amount = satoshiAmountToSend - feeForOneOutput;
|
final int amount = satoshiAmountToSend - feeForOneOutput;
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: [amount],
|
satoshiAmounts: [amount],
|
||||||
|
@ -1399,13 +1399,11 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
||||||
}
|
}
|
||||||
|
|
||||||
final int vSizeForOneOutput = (await buildTransaction(
|
final int vSizeForOneOutput = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [_recipientAddress],
|
recipients: [_recipientAddress],
|
||||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||||
))["vSize"] as int;
|
))["vSize"] as int;
|
||||||
final int vSizeForTwoOutPuts = (await buildTransaction(
|
final int vSizeForTwoOutPuts = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [
|
recipients: [
|
||||||
_recipientAddress,
|
_recipientAddress,
|
||||||
|
@ -1484,7 +1482,6 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
|
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -1512,7 +1509,6 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
||||||
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
|
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
txn = await buildTransaction(
|
txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -1541,7 +1537,6 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -1570,7 +1565,6 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -1599,7 +1593,6 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
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,
|
List<isar_models.UTXO> utxosToUse,
|
||||||
) async {
|
) async {
|
||||||
// return data
|
// return data
|
||||||
Map<String, dynamic> results = {};
|
List<SigningData> signingData = [];
|
||||||
Map<String, List<String>> addressTxid = {};
|
|
||||||
|
|
||||||
// addresses to check
|
|
||||||
List<String> addresses = [];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Populating the addresses to check
|
// Populating the addresses to check
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxosToUse.length; i++) {
|
||||||
|
if (utxosToUse[i].address == null) {
|
||||||
final txid = utxosToUse[i].txid;
|
final txid = utxosToUse[i].txid;
|
||||||
final tx = await _cachedElectrumXClient.getTransaction(
|
final tx = await _cachedElectrumXClient.getTransaction(
|
||||||
txHash: txid,
|
txHash: txid,
|
||||||
coin: coin,
|
coin: coin,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (final output in tx["vout"] as List) {
|
for (final output in tx["vout"] as List) {
|
||||||
final n = output["n"];
|
final n = output["n"];
|
||||||
if (n != null && n == utxosToUse[i].vout) {
|
if (n != null && n == utxosToUse[i].vout) {
|
||||||
final address = output["scriptPubKey"]["addresses"][0] as String;
|
utxosToUse[i] = utxosToUse[i].copyWith(
|
||||||
|
address: output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||||
if (!addressTxid.containsKey(address)) {
|
output["scriptPubKey"]["address"] as String,
|
||||||
addressTxid[address] = <String>[];
|
);
|
||||||
}
|
|
||||||
(addressTxid[address] as List).add(txid);
|
|
||||||
|
|
||||||
addresses.add(address);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// p2pkh / bip44
|
signingData.add(
|
||||||
final addressesLength = addresses.length;
|
SigningData(
|
||||||
if (addressesLength > 0) {
|
derivePathType: DerivePathType.bip44,
|
||||||
final receiveDerivationsString =
|
utxo: utxosToUse[i],
|
||||||
await _secureStore.read(key: "${walletId}_receiveDerivations");
|
),
|
||||||
final receiveDerivations = Map<String, dynamic>.from(
|
);
|
||||||
jsonDecode(receiveDerivationsString ?? "{}") as Map);
|
}
|
||||||
|
|
||||||
final changeDerivationsString =
|
Map<DerivePathType, Map<String, dynamic>> receiveDerivations = {};
|
||||||
await _secureStore.read(key: "${walletId}_changeDerivations");
|
Map<DerivePathType, Map<String, dynamic>> changeDerivations = {};
|
||||||
final changeDerivations = Map<String, dynamic>.from(
|
|
||||||
jsonDecode(changeDerivationsString ?? "{}") as Map);
|
|
||||||
|
|
||||||
for (int i = 0; i < addressesLength; i++) {
|
for (final sd in signingData) {
|
||||||
// receives
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
dynamic receiveDerivation;
|
dynamic receiveDerivation;
|
||||||
|
for (int j = 0;
|
||||||
for (int j = 0; j < receiveDerivations.length; j++) {
|
j < receiveDerivations[sd.derivePathType]!.length &&
|
||||||
if (receiveDerivations["$j"]["address"] == addresses[i]) {
|
receiveDerivation == null;
|
||||||
receiveDerivation = receiveDerivations["$j"];
|
j++) {
|
||||||
|
if (receiveDerivations[sd.derivePathType]!["$j"]["address"] ==
|
||||||
|
sd.utxo.address!) {
|
||||||
|
receiveDerivation = receiveDerivations[sd.derivePathType]!["$j"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// receiveDerivation = receiveDerivations[addresses[i]];
|
|
||||||
// if a match exists it will not be null
|
|
||||||
if (receiveDerivation != null) {
|
if (receiveDerivation != null) {
|
||||||
final data = P2PKH(
|
pubKey = receiveDerivation["publicKey"] as String;
|
||||||
data: PaymentData(
|
wif = receiveDerivation["wif"] as String;
|
||||||
pubkey: Format.stringToUint8List(
|
|
||||||
receiveDerivation["publicKey"] as String)),
|
|
||||||
network: _network,
|
|
||||||
).data;
|
|
||||||
|
|
||||||
for (String tx in addressTxid[addresses[i]]!) {
|
|
||||||
results[tx] = {
|
|
||||||
"output": data.output,
|
|
||||||
"keyPair": ECPair.fromWIF(
|
|
||||||
receiveDerivation["wif"] as String,
|
|
||||||
network: _network,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// if its not a receive, check change
|
// fetch change derivations if null
|
||||||
|
changeDerivations[sd.derivePathType] ??= Map<String, dynamic>.from(
|
||||||
|
jsonDecode((await _secureStore.read(
|
||||||
|
key: "${walletId}_changeDerivations",
|
||||||
|
)) ??
|
||||||
|
"{}") as Map,
|
||||||
|
);
|
||||||
|
|
||||||
dynamic changeDerivation;
|
dynamic changeDerivation;
|
||||||
|
for (int j = 0;
|
||||||
for (int j = 0; j < changeDerivations.length; j++) {
|
j < changeDerivations[sd.derivePathType]!.length &&
|
||||||
if (changeDerivations["$j"]["address"] == addresses[i]) {
|
changeDerivation == null;
|
||||||
changeDerivation = changeDerivations["$j"];
|
j++) {
|
||||||
|
if (changeDerivations[sd.derivePathType]!["$j"]["address"] ==
|
||||||
|
sd.utxo.address!) {
|
||||||
|
changeDerivation = changeDerivations[sd.derivePathType]!["$j"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// final changeDerivation = changeDerivations[addresses[i]];
|
|
||||||
// if a match exists it will not be null
|
|
||||||
if (changeDerivation != null) {
|
if (changeDerivation != null) {
|
||||||
final data = P2PKH(
|
pubKey = changeDerivation["publicKey"] 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(
|
data: PaymentData(
|
||||||
pubkey: Format.stringToUint8List(
|
pubkey: Format.stringToUint8List(pubKey),
|
||||||
changeDerivation["publicKey"] as String)),
|
),
|
||||||
network: _network,
|
network: _network,
|
||||||
).data;
|
).data;
|
||||||
|
redeemScript = null;
|
||||||
|
break;
|
||||||
|
|
||||||
for (String tx in addressTxid[addresses[i]]!) {
|
default:
|
||||||
results[tx] = {
|
throw Exception("DerivePathType unsupported");
|
||||||
"output": data.output,
|
}
|
||||||
"keyPair": ECPair.fromWIF(
|
|
||||||
changeDerivation["wif"] as String,
|
final keyPair = ECPair.fromWIF(
|
||||||
|
wif,
|
||||||
network: _network,
|
network: _network,
|
||||||
),
|
);
|
||||||
};
|
|
||||||
}
|
sd.redeemScript = redeemScript;
|
||||||
}
|
sd.output = data.output;
|
||||||
}
|
sd.keyPair = keyPair;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return signingData;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
|
.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
|
/// Builds and signs a transaction
|
||||||
Future<Map<String, dynamic>> buildTransaction({
|
Future<Map<String, dynamic>> buildTransaction({
|
||||||
required List<isar_models.UTXO> utxosToUse,
|
required List<SigningData> utxoSigningData,
|
||||||
required Map<String, dynamic> utxoSigningData,
|
|
||||||
required List<String> recipients,
|
required List<String> recipients,
|
||||||
required List<int> satoshiAmounts,
|
required List<int> satoshiAmounts,
|
||||||
}) async {
|
}) async {
|
||||||
|
@ -1763,10 +1762,14 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
||||||
txb.setVersion(1);
|
txb.setVersion(1);
|
||||||
|
|
||||||
// Add transaction inputs
|
// Add transaction inputs
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||||
final txid = utxosToUse[i].txid;
|
final txid = utxoSigningData[i].utxo.txid;
|
||||||
txb.addInput(txid, utxosToUse[i].vout, null,
|
txb.addInput(
|
||||||
utxoSigningData[txid]["output"] as Uint8List);
|
txid,
|
||||||
|
utxoSigningData[i].utxo.vout,
|
||||||
|
null,
|
||||||
|
utxoSigningData[i].output!,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add transaction output
|
// Add transaction output
|
||||||
|
@ -1776,13 +1779,12 @@ class FiroWallet extends CoinServiceAPI with WalletCache, WalletDB, FiroHive {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Sign the transaction accordingly
|
// Sign the transaction accordingly
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||||
final txid = utxosToUse[i].txid;
|
|
||||||
txb.sign(
|
txb.sign(
|
||||||
vin: i,
|
vin: i,
|
||||||
keyPair: utxoSigningData[txid]["keyPair"] as ECPair,
|
keyPair: utxoSigningData[i].keyPair!,
|
||||||
witnessValue: utxosToUse[i].value,
|
witnessValue: utxoSigningData[i].utxo.value,
|
||||||
redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?,
|
redeemScript: utxoSigningData[i].redeemScript,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||||
import 'package:stackwallet/models/balance.dart';
|
import 'package:stackwallet/models/balance.dart';
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
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/paymint/fee_object_model.dart';
|
||||||
|
import 'package:stackwallet/models/signing_data.dart';
|
||||||
import 'package:stackwallet/services/coins/coin_service.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/node_connection_status_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_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);
|
.log("Attempting to send all $coin", level: LogLevel.Info);
|
||||||
|
|
||||||
final int vSizeForOneOutput = (await buildTransaction(
|
final int vSizeForOneOutput = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [recipientAddress],
|
recipients: [recipientAddress],
|
||||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||||
|
@ -2356,7 +2356,6 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
|
|
||||||
final int amount = satoshiAmountToSend - feeForOneOutput;
|
final int amount = satoshiAmountToSend - feeForOneOutput;
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: [amount],
|
satoshiAmounts: [amount],
|
||||||
|
@ -2367,19 +2366,17 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": amount,
|
"recipientAmt": amount,
|
||||||
"fee": feeForOneOutput,
|
"fee": feeForOneOutput,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int vSizeForOneOutput = (await buildTransaction(
|
final int vSizeForOneOutput = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [recipientAddress],
|
recipients: [recipientAddress],
|
||||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||||
))["vSize"] as int;
|
))["vSize"] as int;
|
||||||
final int vSizeForTwoOutPuts = (await buildTransaction(
|
final int vSizeForTwoOutPuts = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [
|
recipients: [
|
||||||
recipientAddress,
|
recipientAddress,
|
||||||
|
@ -2445,7 +2442,6 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
|
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2473,7 +2469,6 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
|
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
txn = await buildTransaction(
|
txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2486,7 +2481,7 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": feeBeingPaid,
|
"fee": feeBeingPaid,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2503,7 +2498,6 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2514,7 +2508,7 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
@ -2533,7 +2527,6 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2544,7 +2537,7 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
@ -2563,7 +2556,6 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2574,7 +2566,7 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": feeForOneOutput,
|
"fee": feeForOneOutput,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2600,245 +2592,131 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> fetchBuildTxData(
|
Future<List<SigningData>> fetchBuildTxData(
|
||||||
List<isar_models.UTXO> utxosToUse,
|
List<isar_models.UTXO> utxosToUse,
|
||||||
) async {
|
) async {
|
||||||
// return data
|
// return data
|
||||||
Map<String, dynamic> results = {};
|
List<SigningData> signingData = [];
|
||||||
Map<String, List<String>> addressTxid = {};
|
|
||||||
|
|
||||||
// addresses to check
|
|
||||||
List<String> addressesP2PKH = [];
|
|
||||||
List<String> addressesP2SH = [];
|
|
||||||
List<String> addressesP2WPKH = [];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Populating the addresses to check
|
// Populating the addresses to check
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxosToUse.length; i++) {
|
||||||
|
if (utxosToUse[i].address == null) {
|
||||||
final txid = utxosToUse[i].txid;
|
final txid = utxosToUse[i].txid;
|
||||||
final tx = await _cachedElectrumXClient.getTransaction(
|
final tx = await _cachedElectrumXClient.getTransaction(
|
||||||
txHash: txid,
|
txHash: txid,
|
||||||
coin: coin,
|
coin: coin,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (final output in tx["vout"] as List) {
|
for (final output in tx["vout"] as List) {
|
||||||
final n = output["n"];
|
final n = output["n"];
|
||||||
if (n != null && n == utxosToUse[i].vout) {
|
if (n != null && n == utxosToUse[i].vout) {
|
||||||
final address = output["scriptPubKey"]["addresses"][0] as String;
|
utxosToUse[i] = utxosToUse[i].copyWith(
|
||||||
if (!addressTxid.containsKey(address)) {
|
address: output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||||
addressTxid[address] = <String>[];
|
output["scriptPubKey"]["address"] as String,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
(addressTxid[address] as List).add(txid);
|
}
|
||||||
switch (addressType(address: address)) {
|
}
|
||||||
|
|
||||||
|
final derivePathType = addressType(address: utxosToUse[i].address!);
|
||||||
|
|
||||||
|
signingData.add(
|
||||||
|
SigningData(
|
||||||
|
derivePathType: derivePathType,
|
||||||
|
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] ??= 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:
|
case DerivePathType.bip44:
|
||||||
addressesP2PKH.add(address);
|
data = P2PKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey: Format.stringToUint8List(pubKey),
|
||||||
|
),
|
||||||
|
network: _network,
|
||||||
|
).data;
|
||||||
|
redeemScript = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DerivePathType.bip49:
|
case DerivePathType.bip49:
|
||||||
addressesP2SH.add(address);
|
final p2wpkh = P2WPKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey: Format.stringToUint8List(pubKey),
|
||||||
|
),
|
||||||
|
network: _network,
|
||||||
|
overridePrefix: _network.bech32!,
|
||||||
|
).data;
|
||||||
|
redeemScript = p2wpkh.output;
|
||||||
|
data = P2SH(
|
||||||
|
data: PaymentData(redeem: p2wpkh),
|
||||||
|
network: _network,
|
||||||
|
).data;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DerivePathType.bip84:
|
case DerivePathType.bip84:
|
||||||
addressesP2WPKH.add(address);
|
data = P2WPKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey: Format.stringToUint8List(pubKey),
|
||||||
|
),
|
||||||
|
network: _network,
|
||||||
|
overridePrefix: _network.bech32!,
|
||||||
|
).data;
|
||||||
|
redeemScript = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw Exception("DerivePathType unsupported");
|
throw Exception("DerivePathType unsupported");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// p2pkh / bip44
|
final keyPair = ECPair.fromWIF(
|
||||||
final p2pkhLength = addressesP2PKH.length;
|
wif,
|
||||||
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,
|
network: _network,
|
||||||
).data;
|
|
||||||
|
|
||||||
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(
|
|
||||||
data: PaymentData(
|
|
||||||
pubkey: Format.stringToUint8List(
|
|
||||||
changeDerivation["pubKey"] as String)),
|
|
||||||
network: _network,
|
|
||||||
).data;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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++) {
|
sd.redeemScript = redeemScript;
|
||||||
// receives
|
sd.output = data.output;
|
||||||
final receiveDerivation = receiveDerivations[addressesP2WPKH[i]];
|
sd.keyPair = keyPair;
|
||||||
// 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,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} 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;
|
|
||||||
|
|
||||||
for (String tx in addressTxid[addressesP2WPKH[i]]!) {
|
|
||||||
results[tx] = {
|
|
||||||
"output": data.output,
|
|
||||||
"keyPair": ECPair.fromWIF(
|
|
||||||
changeDerivation["wif"] as String,
|
|
||||||
network: _network,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return signingData;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
|
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
|
||||||
|
@ -2848,8 +2726,7 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
|
|
||||||
/// Builds and signs a transaction
|
/// Builds and signs a transaction
|
||||||
Future<Map<String, dynamic>> buildTransaction({
|
Future<Map<String, dynamic>> buildTransaction({
|
||||||
required List<isar_models.UTXO> utxosToUse,
|
required List<SigningData> utxoSigningData,
|
||||||
required Map<String, dynamic> utxoSigningData,
|
|
||||||
required List<String> recipients,
|
required List<String> recipients,
|
||||||
required List<int> satoshiAmounts,
|
required List<int> satoshiAmounts,
|
||||||
}) async {
|
}) async {
|
||||||
|
@ -2860,10 +2737,15 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
txb.setVersion(1);
|
txb.setVersion(1);
|
||||||
|
|
||||||
// Add transaction inputs
|
// Add transaction inputs
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||||
final txid = utxosToUse[i].txid;
|
final txid = utxoSigningData[i].utxo.txid;
|
||||||
txb.addInput(txid, utxosToUse[i].vout, null,
|
txb.addInput(
|
||||||
utxoSigningData[txid]["output"] as Uint8List, _network.bech32!);
|
txid,
|
||||||
|
utxoSigningData[i].utxo.vout,
|
||||||
|
null,
|
||||||
|
utxoSigningData[i].output!,
|
||||||
|
_network.bech32!,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add transaction output
|
// Add transaction output
|
||||||
|
@ -2873,14 +2755,14 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Sign the transaction accordingly
|
// Sign the transaction accordingly
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||||
final txid = utxosToUse[i].txid;
|
|
||||||
txb.sign(
|
txb.sign(
|
||||||
vin: i,
|
vin: i,
|
||||||
keyPair: utxoSigningData[txid]["keyPair"] as ECPair,
|
keyPair: utxoSigningData[i].keyPair!,
|
||||||
witnessValue: utxosToUse[i].value,
|
witnessValue: utxoSigningData[i].utxo.value,
|
||||||
redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?,
|
redeemScript: utxoSigningData[i].redeemScript,
|
||||||
overridePrefix: _network.bech32!);
|
overridePrefix: _network.bech32!,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance.log("Caught exception while signing transaction: $e\n$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/balance.dart';
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
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/paymint/fee_object_model.dart';
|
||||||
|
import 'package:stackwallet/models/signing_data.dart';
|
||||||
import 'package:stackwallet/services/coins/coin_service.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/node_connection_status_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_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);
|
.log("Attempting to send all $coin", level: LogLevel.Info);
|
||||||
|
|
||||||
final int vSizeForOneOutput = (await buildTransaction(
|
final int vSizeForOneOutput = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [recipientAddress],
|
recipients: [recipientAddress],
|
||||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||||
|
@ -2349,7 +2349,6 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
|
|
||||||
final int amount = satoshiAmountToSend - feeForOneOutput;
|
final int amount = satoshiAmountToSend - feeForOneOutput;
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: [amount],
|
satoshiAmounts: [amount],
|
||||||
|
@ -2360,19 +2359,17 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": amount,
|
"recipientAmt": amount,
|
||||||
"fee": feeForOneOutput,
|
"fee": feeForOneOutput,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int vSizeForOneOutput = (await buildTransaction(
|
final int vSizeForOneOutput = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [recipientAddress],
|
recipients: [recipientAddress],
|
||||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||||
))["vSize"] as int;
|
))["vSize"] as int;
|
||||||
final int vSizeForTwoOutPuts = (await buildTransaction(
|
final int vSizeForTwoOutPuts = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [
|
recipients: [
|
||||||
recipientAddress,
|
recipientAddress,
|
||||||
|
@ -2438,7 +2435,6 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
|
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2466,7 +2462,6 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
|
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
txn = await buildTransaction(
|
txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2479,7 +2474,7 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": feeBeingPaid,
|
"fee": feeBeingPaid,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2496,7 +2491,6 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2507,7 +2501,7 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
@ -2526,7 +2520,6 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2537,7 +2530,7 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
@ -2556,7 +2549,6 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2567,7 +2559,7 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": feeForOneOutput,
|
"fee": feeForOneOutput,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2593,248 +2585,129 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> fetchBuildTxData(
|
Future<List<SigningData>> fetchBuildTxData(
|
||||||
List<isar_models.UTXO> utxosToUse,
|
List<isar_models.UTXO> utxosToUse,
|
||||||
) async {
|
) async {
|
||||||
// return data
|
// return data
|
||||||
Map<String, dynamic> results = {};
|
List<SigningData> signingData = [];
|
||||||
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);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Populating the addresses to check
|
// Populating the addresses to check
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxosToUse.length; i++) {
|
||||||
|
if (utxosToUse[i].address == null) {
|
||||||
final txid = utxosToUse[i].txid;
|
final txid = utxosToUse[i].txid;
|
||||||
final tx = await _cachedElectrumXClient.getTransaction(
|
final tx = await _cachedElectrumXClient.getTransaction(
|
||||||
txHash: txid,
|
txHash: txid,
|
||||||
coin: coin,
|
coin: coin,
|
||||||
);
|
);
|
||||||
Logging.instance.log("tx: ${json.encode(tx)}",
|
|
||||||
level: LogLevel.Info, printFullLength: true);
|
|
||||||
|
|
||||||
for (final output in tx["vout"] as List) {
|
for (final output in tx["vout"] as List) {
|
||||||
final n = output["n"];
|
final n = output["n"];
|
||||||
if (n != null && n == utxosToUse[i].vout) {
|
if (n != null && n == utxosToUse[i].vout) {
|
||||||
final address = output["scriptPubKey"]["address"] as String;
|
utxosToUse[i] = utxosToUse[i].copyWith(
|
||||||
if (!addressTxid.containsKey(address)) {
|
address: output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||||
addressTxid[address] = <String>[];
|
output["scriptPubKey"]["address"] as String,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
(addressTxid[address] as List).add(txid);
|
}
|
||||||
switch (addressType(address: address)) {
|
}
|
||||||
|
|
||||||
|
final derivePathType = addressType(address: utxosToUse[i].address!);
|
||||||
|
|
||||||
|
signingData.add(
|
||||||
|
SigningData(
|
||||||
|
derivePathType: derivePathType,
|
||||||
|
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] ??= 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:
|
case DerivePathType.bip44:
|
||||||
addressesP2PKH.add(address);
|
data = P2PKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey: Format.stringToUint8List(pubKey),
|
||||||
|
),
|
||||||
|
network: _network,
|
||||||
|
).data;
|
||||||
|
redeemScript = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DerivePathType.bip49:
|
case DerivePathType.bip49:
|
||||||
addressesP2SH.add(address);
|
final p2wpkh = P2WPKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey: Format.stringToUint8List(pubKey),
|
||||||
|
),
|
||||||
|
network: _network,
|
||||||
|
overridePrefix: _network.bech32!,
|
||||||
|
).data;
|
||||||
|
redeemScript = p2wpkh.output;
|
||||||
|
data = P2SH(data: PaymentData(redeem: p2wpkh), network: _network)
|
||||||
|
.data;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DerivePathType.bip84:
|
case DerivePathType.bip84:
|
||||||
addressesP2WPKH.add(address);
|
data = P2WPKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey: Format.stringToUint8List(pubKey),
|
||||||
|
),
|
||||||
|
network: _network,
|
||||||
|
overridePrefix: _network.bech32!,
|
||||||
|
).data;
|
||||||
|
redeemScript = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw Exception("DerivePathType unsupported");
|
throw Exception("DerivePathType unsupported");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// p2pkh / bip44
|
final keyPair = ECPair.fromWIF(
|
||||||
final p2pkhLength = addressesP2PKH.length;
|
wif,
|
||||||
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,
|
network: _network,
|
||||||
).data;
|
|
||||||
|
|
||||||
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(
|
|
||||||
data: PaymentData(
|
|
||||||
pubkey: Format.stringToUint8List(
|
|
||||||
changeDerivation["pubKey"] as String)),
|
|
||||||
network: _network,
|
|
||||||
).data;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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++) {
|
sd.redeemScript = redeemScript;
|
||||||
// receives
|
sd.output = data.output;
|
||||||
final receiveDerivation = receiveDerivations[addressesP2WPKH[i]];
|
sd.keyPair = keyPair;
|
||||||
// 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,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} 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!)
|
|
||||||
.data;
|
|
||||||
|
|
||||||
for (String tx in addressTxid[addressesP2WPKH[i]]!) {
|
|
||||||
results[tx] = {
|
|
||||||
"output": data.output,
|
|
||||||
"keyPair": ECPair.fromWIF(
|
|
||||||
changeDerivation["wif"] as String,
|
|
||||||
network: _network,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return signingData;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
|
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
|
||||||
|
@ -2844,8 +2717,7 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
|
|
||||||
/// Builds and signs a transaction
|
/// Builds and signs a transaction
|
||||||
Future<Map<String, dynamic>> buildTransaction({
|
Future<Map<String, dynamic>> buildTransaction({
|
||||||
required List<isar_models.UTXO> utxosToUse,
|
required List<SigningData> utxoSigningData,
|
||||||
required Map<String, dynamic> utxoSigningData,
|
|
||||||
required List<String> recipients,
|
required List<String> recipients,
|
||||||
required List<int> satoshiAmounts,
|
required List<int> satoshiAmounts,
|
||||||
}) async {
|
}) async {
|
||||||
|
@ -2856,10 +2728,15 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
txb.setVersion(2);
|
txb.setVersion(2);
|
||||||
|
|
||||||
// Add transaction inputs
|
// Add transaction inputs
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||||
final txid = utxosToUse[i].txid;
|
final txid = utxoSigningData[i].utxo.txid;
|
||||||
txb.addInput(txid, utxosToUse[i].vout, null,
|
txb.addInput(
|
||||||
utxoSigningData[txid]["output"] as Uint8List, namecoin.bech32!);
|
txid,
|
||||||
|
utxoSigningData[i].utxo.vout,
|
||||||
|
null,
|
||||||
|
utxoSigningData[i].output!,
|
||||||
|
_network.bech32!,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add transaction output
|
// Add transaction output
|
||||||
|
@ -2869,14 +2746,15 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Sign the transaction accordingly
|
// Sign the transaction accordingly
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||||
final txid = utxosToUse[i].txid;
|
final txid = utxoSigningData[i].utxo.txid;
|
||||||
txb.sign(
|
txb.addInput(
|
||||||
vin: i,
|
txid,
|
||||||
keyPair: utxoSigningData[txid]["keyPair"] as ECPair,
|
utxoSigningData[i].utxo.vout,
|
||||||
witnessValue: utxosToUse[i].value,
|
null,
|
||||||
redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?,
|
utxoSigningData[i].output!,
|
||||||
overridePrefix: namecoin.bech32!);
|
_network.bech32!,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance.log("Caught exception while signing transaction: $e\n$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/balance.dart';
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models;
|
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/paymint/fee_object_model.dart';
|
||||||
|
import 'package:stackwallet/models/signing_data.dart';
|
||||||
import 'package:stackwallet/services/coins/coin_service.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/node_connection_status_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/events/global/refresh_percent_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);
|
.log("Attempting to send all $coin", level: LogLevel.Info);
|
||||||
|
|
||||||
final int vSizeForOneOutput = (await buildTransaction(
|
final int vSizeForOneOutput = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [recipientAddress],
|
recipients: [recipientAddress],
|
||||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||||
|
@ -2510,7 +2510,6 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
|
|
||||||
final int amount = satoshiAmountToSend - feeForOneOutput;
|
final int amount = satoshiAmountToSend - feeForOneOutput;
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: [amount],
|
satoshiAmounts: [amount],
|
||||||
|
@ -2521,19 +2520,17 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
"recipientAmt": amount,
|
"recipientAmt": amount,
|
||||||
"fee": feeForOneOutput,
|
"fee": feeForOneOutput,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int vSizeForOneOutput = (await buildTransaction(
|
final int vSizeForOneOutput = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [recipientAddress],
|
recipients: [recipientAddress],
|
||||||
satoshiAmounts: [satoshisBeingUsed - 1],
|
satoshiAmounts: [satoshisBeingUsed - 1],
|
||||||
))["vSize"] as int;
|
))["vSize"] as int;
|
||||||
final int vSizeForTwoOutPuts = (await buildTransaction(
|
final int vSizeForTwoOutPuts = (await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: [
|
recipients: [
|
||||||
recipientAddress,
|
recipientAddress,
|
||||||
|
@ -2599,7 +2596,6 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
|
.log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2627,7 +2623,6 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
|
Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs',
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info);
|
||||||
txn = await buildTransaction(
|
txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2640,7 +2635,7 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": feeBeingPaid,
|
"fee": feeBeingPaid,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2657,7 +2652,6 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2668,7 +2662,7 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
@ -2687,7 +2681,6 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2698,7 +2691,7 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
"fee": satoshisBeingUsed - satoshiAmountToSend,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
}
|
}
|
||||||
|
@ -2717,7 +2710,6 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
.log('Estimated fee: $feeForOneOutput', level: LogLevel.Info);
|
||||||
dynamic txn = await buildTransaction(
|
dynamic txn = await buildTransaction(
|
||||||
utxosToUse: utxoObjectsToUse,
|
|
||||||
utxoSigningData: utxoSigningData,
|
utxoSigningData: utxoSigningData,
|
||||||
recipients: recipientsArray,
|
recipients: recipientsArray,
|
||||||
satoshiAmounts: recipientsAmtArray,
|
satoshiAmounts: recipientsAmtArray,
|
||||||
|
@ -2728,7 +2720,7 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
"recipientAmt": recipientsAmtArray[0],
|
"recipientAmt": recipientsAmtArray[0],
|
||||||
"fee": feeForOneOutput,
|
"fee": feeForOneOutput,
|
||||||
"vSize": txn["vSize"],
|
"vSize": txn["vSize"],
|
||||||
"usedUTXOs": utxoObjectsToUse,
|
"usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(),
|
||||||
};
|
};
|
||||||
return transactionObject;
|
return transactionObject;
|
||||||
} else {
|
} else {
|
||||||
|
@ -2754,168 +2746,115 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> fetchBuildTxData(
|
Future<List<SigningData>> fetchBuildTxData(
|
||||||
List<isar_models.UTXO> utxosToUse,
|
List<isar_models.UTXO> utxosToUse,
|
||||||
) async {
|
) async {
|
||||||
// return data
|
// return data
|
||||||
Map<String, dynamic> results = {};
|
List<SigningData> signingData = [];
|
||||||
Map<String, List<String>> addressTxid = {};
|
|
||||||
|
|
||||||
// addresses to check
|
|
||||||
List<String> addressesP2PKH = [];
|
|
||||||
List<String> addressesP2WPKH = [];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Populating the addresses to check
|
// Populating the addresses to check
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxosToUse.length; i++) {
|
||||||
|
if (utxosToUse[i].address == null) {
|
||||||
final txid = utxosToUse[i].txid;
|
final txid = utxosToUse[i].txid;
|
||||||
final tx = await _cachedElectrumXClient.getTransaction(
|
final tx = await _cachedElectrumXClient.getTransaction(
|
||||||
txHash: txid,
|
txHash: txid,
|
||||||
coin: coin,
|
coin: coin,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (final output in tx["vout"] as List) {
|
for (final output in tx["vout"] as List) {
|
||||||
final n = output["n"];
|
final n = output["n"];
|
||||||
if (n != null && n == utxosToUse[i].vout) {
|
if (n != null && n == utxosToUse[i].vout) {
|
||||||
final address = output["scriptPubKey"]["addresses"][0] as String;
|
utxosToUse[i] = utxosToUse[i].copyWith(
|
||||||
if (!addressTxid.containsKey(address)) {
|
address: output["scriptPubKey"]?["addresses"]?[0] as String? ??
|
||||||
addressTxid[address] = <String>[];
|
output["scriptPubKey"]["address"] as String,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
(addressTxid[address] as List).add(txid);
|
}
|
||||||
switch (addressType(address: address)) {
|
}
|
||||||
|
|
||||||
|
final derivePathType = addressType(address: utxosToUse[i].address!);
|
||||||
|
|
||||||
|
signingData.add(
|
||||||
|
SigningData(
|
||||||
|
derivePathType: derivePathType,
|
||||||
|
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] ??= 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:
|
case DerivePathType.bip44:
|
||||||
addressesP2PKH.add(address);
|
data = P2PKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey: Format.stringToUint8List(pubKey),
|
||||||
|
),
|
||||||
|
network: _network,
|
||||||
|
).data;
|
||||||
|
redeemScript = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DerivePathType.bip84:
|
case DerivePathType.bip84:
|
||||||
addressesP2WPKH.add(address);
|
data = P2WPKH(
|
||||||
|
data: PaymentData(
|
||||||
|
pubkey: Format.stringToUint8List(pubKey),
|
||||||
|
),
|
||||||
|
network: _network,
|
||||||
|
).data;
|
||||||
|
redeemScript = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw Exception(
|
throw Exception("DerivePathType unsupported");
|
||||||
"DerivePathType ${addressType(address: address)} not supported");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// p2pkh / bip44
|
final keyPair = ECPair.fromWIF(
|
||||||
final p2pkhLength = addressesP2PKH.length;
|
wif,
|
||||||
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,
|
network: _network,
|
||||||
).data;
|
|
||||||
|
|
||||||
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(
|
|
||||||
data: PaymentData(
|
|
||||||
pubkey: Format.stringToUint8List(
|
|
||||||
changeDerivation["pubKey"] as String)),
|
|
||||||
network: _network,
|
|
||||||
).data;
|
|
||||||
|
|
||||||
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++) {
|
sd.redeemScript = redeemScript;
|
||||||
// receives
|
sd.output = data.output;
|
||||||
final receiveDerivation = receiveDerivations[addressesP2WPKH[i]];
|
sd.keyPair = keyPair;
|
||||||
// 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]]!) {
|
return signingData;
|
||||||
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)),
|
|
||||||
network: _network,
|
|
||||||
).data;
|
|
||||||
|
|
||||||
for (String tx in addressTxid[addressesP2WPKH[i]]!) {
|
|
||||||
results[tx] = {
|
|
||||||
"output": data.output,
|
|
||||||
"keyPair": ECPair.fromWIF(
|
|
||||||
changeDerivation["wif"] as String,
|
|
||||||
network: _network,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Logging.instance.log("FETCHED TX BUILD DATA IS -----$results",
|
|
||||||
level: LogLevel.Info, printFullLength: true);
|
|
||||||
return results;
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance
|
Logging.instance
|
||||||
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
|
.log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error);
|
||||||
|
@ -2925,8 +2864,7 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
|
|
||||||
/// Builds and signs a transaction
|
/// Builds and signs a transaction
|
||||||
Future<Map<String, dynamic>> buildTransaction({
|
Future<Map<String, dynamic>> buildTransaction({
|
||||||
required List<isar_models.UTXO> utxosToUse,
|
required List<SigningData> utxoSigningData,
|
||||||
required Map<String, dynamic> utxoSigningData,
|
|
||||||
required List<String> recipients,
|
required List<String> recipients,
|
||||||
required List<int> satoshiAmounts,
|
required List<int> satoshiAmounts,
|
||||||
}) async {
|
}) async {
|
||||||
|
@ -2940,11 +2878,15 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
txb.setVersion(160);
|
txb.setVersion(160);
|
||||||
|
|
||||||
// Add transaction inputs
|
// Add transaction inputs
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||||
final txid = utxosToUse[i].txid;
|
final txid = utxoSigningData[i].utxo.txid;
|
||||||
|
txb.addInput(
|
||||||
txb.addInput(txid, utxosToUse[i].vout, null,
|
txid,
|
||||||
utxoSigningData[txid]["output"] as Uint8List, '');
|
utxoSigningData[i].utxo.vout,
|
||||||
|
null,
|
||||||
|
utxoSigningData[i].output!,
|
||||||
|
'',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add transaction output
|
// Add transaction output
|
||||||
|
@ -2954,13 +2896,13 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Sign the transaction accordingly
|
// Sign the transaction accordingly
|
||||||
for (var i = 0; i < utxosToUse.length; i++) {
|
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||||
final txid = utxosToUse[i].txid;
|
|
||||||
txb.sign(
|
txb.sign(
|
||||||
vin: i,
|
vin: i,
|
||||||
keyPair: utxoSigningData[txid]["keyPair"] as ECPair,
|
keyPair: utxoSigningData[i].keyPair!,
|
||||||
witnessValue: utxosToUse[i].value,
|
witnessValue: utxoSigningData[i].utxo.value,
|
||||||
redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?);
|
redeemScript: utxoSigningData[i].redeemScript,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance.log("Caught exception while signing transaction: $e\n$s",
|
Logging.instance.log("Caught exception while signing transaction: $e\n$s",
|
||||||
|
|
Loading…
Reference in a new issue