handle send all spark properly

This commit is contained in:
julian 2023-12-21 14:41:29 -06:00
parent 1e1a472d42
commit b441157398
5 changed files with 120 additions and 29 deletions

View file

@ -141,7 +141,11 @@ class CachedElectrumXClient {
set["blockHash"] = newSet["blockHash"]; set["blockHash"] = newSet["blockHash"];
for (int i = (newSet["coins"] as List).length - 1; i >= 0; i--) { for (int i = (newSet["coins"] as List).length - 1; i >= 0; i--) {
// TODO verify this is correct (or append?) // TODO verify this is correct (or append?)
set["coins"].insert(0, newSet["coins"][i]); if ((set["coins"] as List)
.where((e) => e[0] == newSet["coins"][i][0])
.isEmpty) {
set["coins"].insert(0, newSet["coins"][i]);
}
} }
// save set to db // save set to db
await box.put(groupId, set); await box.put(groupId, set);

View file

@ -535,7 +535,6 @@ class _SendViewState extends ConsumerState<SendView> {
address: _address!, address: _address!,
amount: amount, amount: amount,
memo: memoController.text, memo: memoController.text,
subtractFeeFromAmount: false,
) )
] ]
: null, : null,
@ -570,7 +569,6 @@ class _SendViewState extends ConsumerState<SendView> {
address: _address!, address: _address!,
amount: amount, amount: amount,
memo: memoController.text, memo: memoController.text,
subtractFeeFromAmount: false,
) )
] ]
: null, : null,

View file

@ -332,7 +332,6 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
address: _address!, address: _address!,
amount: amount, amount: amount,
memo: memoController.text, memo: memoController.text,
subtractFeeFromAmount: false,
) )
] ]
: null, : null,
@ -367,7 +366,6 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
address: _address!, address: _address!,
amount: amount, amount: amount,
memo: memoController.text, memo: memoController.text,
subtractFeeFromAmount: false,
) )
] ]
: null, : null,

View file

@ -60,7 +60,6 @@ class TxData {
({ ({
String address, String address,
Amount amount, Amount amount,
bool subtractFeeFromAmount,
String memo, String memo,
})>? sparkRecipients; })>? sparkRecipients;
@ -148,7 +147,6 @@ class TxData {
({ ({
String address, String address,
Amount amount, Amount amount,
bool subtractFeeFromAmount,
String memo, String memo,
})>? })>?
sparkRecipients, sparkRecipients,

View file

@ -125,8 +125,32 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
.isUsedEqualTo(false) .isUsedEqualTo(false)
.and() .and()
.heightIsNotNull() .heightIsNotNull()
.and()
.not()
.valueIntStringEqualTo("0")
.findAll(); .findAll();
final available = info.cachedBalanceTertiary.spendable;
final txAmount = (txData.recipients ?? []).map((e) => e.amount).fold(
Amount(
rawValue: BigInt.zero,
fractionDigits: cryptoCurrency.fractionDigits,
),
(p, e) => p + e) +
(txData.sparkRecipients ?? []).map((e) => e.amount).fold(
Amount(
rawValue: BigInt.zero,
fractionDigits: cryptoCurrency.fractionDigits,
),
(p, e) => p + e);
if (txAmount > available) {
throw Exception("Insufficient Spark balance");
}
final bool isSendAll = available == txAmount;
// prepare coin data for ffi // prepare coin data for ffi
final serializedCoins = coins final serializedCoins = coins
.map((e) => ( .map((e) => (
@ -177,34 +201,89 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
} }
final privateKey = root.derivePath(derivationPath).privateKey.data; final privateKey = root.derivePath(derivationPath).privateKey.data;
final txb = btc.TransactionBuilder( final btcDartNetwork = btc.NetworkType(
network: btc.NetworkType( messagePrefix: cryptoCurrency.networkParams.messagePrefix,
messagePrefix: cryptoCurrency.networkParams.messagePrefix, bech32: cryptoCurrency.networkParams.bech32Hrp,
bech32: cryptoCurrency.networkParams.bech32Hrp, bip32: btc.Bip32Type(
bip32: btc.Bip32Type( public: cryptoCurrency.networkParams.pubHDPrefix,
public: cryptoCurrency.networkParams.pubHDPrefix, private: cryptoCurrency.networkParams.privHDPrefix,
private: cryptoCurrency.networkParams.privHDPrefix,
),
pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix,
scriptHash: cryptoCurrency.networkParams.p2shPrefix,
wif: cryptoCurrency.networkParams.wifPrefix,
), ),
pubKeyHash: cryptoCurrency.networkParams.p2pkhPrefix,
scriptHash: cryptoCurrency.networkParams.p2shPrefix,
wif: cryptoCurrency.networkParams.wifPrefix,
);
final txb = btc.TransactionBuilder(
network: btcDartNetwork,
); );
txb.setLockTime(await chainHeight); txb.setLockTime(await chainHeight);
txb.setVersion(3 | (9 << 16)); txb.setVersion(3 | (9 << 16));
final List<({String address, Amount amount})> recipientsWithFeeSubtracted =
[];
final List<
({
String address,
Amount amount,
String memo,
})> sparkRecipientsWithFeeSubtracted = [];
final outputCount = (txData.recipients
?.where(
(e) => e.amount.raw > BigInt.zero,
)
.length ??
0) +
(txData.sparkRecipients?.length ?? 0);
final BigInt estimatedFee;
if (isSendAll) {
final estFee = LibSpark.estimateSparkFee(
privateKeyHex: privateKey.toHex,
index: kDefaultSparkIndex,
sendAmount: txAmount.raw.toInt(),
subtractFeeFromAmount: true,
serializedCoins: serializedCoins,
privateRecipientsCount: (txData.sparkRecipients?.length ?? 0),
);
estimatedFee = BigInt.from(estFee);
} else {
estimatedFee = BigInt.zero;
}
for (int i = 0; i < (txData.sparkRecipients?.length ?? 0); i++) {
sparkRecipientsWithFeeSubtracted.add(
(
address: txData.sparkRecipients![i].address,
amount: Amount(
rawValue: txData.sparkRecipients![i].amount.raw -
(estimatedFee ~/ BigInt.from(outputCount)),
fractionDigits: cryptoCurrency.fractionDigits,
),
memo: txData.sparkRecipients![i].memo,
),
);
}
for (int i = 0; i < (txData.recipients?.length ?? 0); i++) { for (int i = 0; i < (txData.recipients?.length ?? 0); i++) {
if (txData.recipients![i].amount.raw == BigInt.zero) { if (txData.recipients![i].amount.raw == BigInt.zero) {
continue; continue;
} }
if (txData.recipients![i].amount < cryptoCurrency.dustLimit) { recipientsWithFeeSubtracted.add(
throw Exception("Output below dust limit"); (
} address: txData.recipients![i].address,
// amount: Amount(
// transparentOut += txData.recipients![i].amount.raw.toInt(); rawValue: txData.recipients![i].amount.raw -
txb.addOutput( (estimatedFee ~/ BigInt.from(outputCount)),
fractionDigits: cryptoCurrency.fractionDigits,
),
),
);
final scriptPubKey = btc.Address.addressToOutputScript(
txData.recipients![i].address, txData.recipients![i].address,
txData.recipients![i].amount.raw.toInt(), btcDartNetwork,
);
txb.addOutput(
scriptPubKey,
recipientsWithFeeSubtracted[i].amount.raw.toInt(),
); );
} }
@ -221,12 +300,19 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
final spend = LibSpark.createSparkSendTransaction( final spend = LibSpark.createSparkSendTransaction(
privateKeyHex: privateKey.toHex, privateKeyHex: privateKey.toHex,
index: kDefaultSparkIndex, index: kDefaultSparkIndex,
recipients: [], recipients: txData.recipients
?.map((e) => (
address: e.address,
amount: e.amount.raw.toInt(),
subtractFeeFromAmount: isSendAll,
))
.toList() ??
[],
privateRecipients: txData.sparkRecipients privateRecipients: txData.sparkRecipients
?.map((e) => ( ?.map((e) => (
sparkAddress: e.address, sparkAddress: e.address,
amount: e.amount.raw.toInt(), amount: e.amount.raw.toInt(),
subtractFeeFromAmount: e.subtractFeeFromAmount, subtractFeeFromAmount: isSendAll,
memo: e.memo, memo: e.memo,
)) ))
.toList() ?? .toList() ??
@ -246,6 +332,13 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
extractedTx.setPayload(spend.serializedSpendPayload); extractedTx.setPayload(spend.serializedSpendPayload);
final rawTxHex = extractedTx.toHex(); final rawTxHex = extractedTx.toHex();
if (isSendAll) {
txData = txData.copyWith(
recipients: recipientsWithFeeSubtracted,
sparkRecipients: sparkRecipientsWithFeeSubtracted,
);
}
return txData.copyWith( return txData.copyWith(
raw: rawTxHex, raw: rawTxHex,
vSize: extractedTx.virtualSize(), vSize: extractedTx.virtualSize(),
@ -279,8 +372,8 @@ mixin SparkInterface on Bip39HDWallet, ElectrumXInterface {
txHash: txHash, txHash: txHash,
txid: txHash, txid: txHash,
); );
// mark utxos as used // // mark utxos as used
await mainDB.putUTXOs(txData.usedUTXOs!); // await mainDB.putUTXOs(txData.usedUTXOs!);
return txData; return txData;
} catch (e, s) { } catch (e, s) {