public async Task <(IBitcoinBasedTransaction, byte[])> CreateSwapPaymentTxAsync( BitcoinBasedCurrency currency, long amount, IEnumerable <string> fromWallets, string refundAddress, string toAddress, DateTimeOffset lockTime, byte[] secretHash, int secretSize, ITxOutputSource outputsSource) { var availableOutputs = (await outputsSource .GetAvailableOutputsAsync(fromWallets) .ConfigureAwait(false)) .ToList(); if (!availableOutputs.Any()) { throw new Exception($"Insufficient funds. Available 0."); } var feeInSatoshi = 0L; ITxOutput[] selectedOutputs = null; for (var i = 1; i <= availableOutputs.Count; ++i) { selectedOutputs = availableOutputs .Take(i) .ToArray(); var estimatedSigSize = BitcoinBasedCurrency.EstimateSigSize(selectedOutputs); var selectedInSatoshi = selectedOutputs.Sum(o => o.Value); if (selectedInSatoshi < amount) // insufficient funds { continue; } var maxFeeInSatoshi = selectedInSatoshi - amount; var estimatedTx = currency .CreateHtlcP2PkhScriptSwapPaymentTx( unspentOutputs: selectedOutputs, aliceRefundAddress: refundAddress, bobAddress: toAddress, lockTime: lockTime, secretHash: secretHash, secretSize: secretSize, amount: amount, fee: maxFeeInSatoshi, redeemScript: out _); var estimatedTxVirtualSize = estimatedTx.VirtualSize(); var estimatedTxSize = estimatedTxVirtualSize + estimatedSigSize; var estimatedTxSizeWithChange = estimatedTxVirtualSize + estimatedSigSize + BitcoinBasedCurrency.OutputSize; var estimatedFeeInSatoshi = (long)(estimatedTxSize * currency.FeeRate); if (estimatedFeeInSatoshi > maxFeeInSatoshi) // insufficient funds { continue; } var estimatedChangeInSatoshi = selectedInSatoshi - amount - estimatedFeeInSatoshi; // if estimated change is dust if (estimatedChangeInSatoshi >= 0 && estimatedChangeInSatoshi < currency.GetDust()) { feeInSatoshi = estimatedFeeInSatoshi + estimatedChangeInSatoshi; break; } // if estimated change > dust var estimatedFeeWithChangeInSatoshi = (long)(estimatedTxSizeWithChange * currency.FeeRate); if (estimatedFeeWithChangeInSatoshi > maxFeeInSatoshi) // insufficient funds { continue; } var esitmatedNewChangeInSatoshi = selectedInSatoshi - amount - estimatedFeeWithChangeInSatoshi; // if new estimated change is dust if (esitmatedNewChangeInSatoshi >= 0 && esitmatedNewChangeInSatoshi < currency.GetDust()) { feeInSatoshi = estimatedFeeWithChangeInSatoshi + esitmatedNewChangeInSatoshi; break; } // if new estimated change > dust feeInSatoshi = estimatedFeeWithChangeInSatoshi; break; } if (selectedOutputs == null || feeInSatoshi == 0L) { throw new Exception($"Insufficient funds. Available {availableOutputs.Sum(o => o.Value)}"); } var tx = currency .CreateHtlcP2PkhScriptSwapPaymentTx( unspentOutputs: selectedOutputs, aliceRefundAddress: refundAddress, bobAddress: toAddress, lockTime: lockTime, secretHash: secretHash, secretSize: secretSize, amount: amount, fee: feeInSatoshi, redeemScript: out var redeemScript); return(tx, redeemScript); }
public async Task <IBitcoinBasedTransaction> CreateSwapPaymentTxAsync( BitcoinBasedCurrency currency, ClientSwap swap, IEnumerable <string> fromWallets, string refundAddress, string toAddress, DateTimeOffset lockTime, byte[] secretHash, int secretSize, ITxOutputSource outputsSource) { var availableOutputs = (await outputsSource .GetAvailableOutputsAsync(currency, fromWallets) .ConfigureAwait(false)) .ToList(); var fee = 0L; var orderAmount = (long)(AmountHelper.QtyToAmount(swap.Side, swap.Qty, swap.Price) * currency.DigitsMultiplier); var requiredAmount = orderAmount + fee; long usedAmount; IList <ITxOutput> usedOutputs; IBitcoinBasedTransaction tx; do { usedOutputs = availableOutputs .SelectOutputsForAmount(requiredAmount) .ToList(); usedAmount = usedOutputs.Sum(o => o.Value); if (usedAmount < requiredAmount) { throw new Exception($"Insufficient funds. Available {usedAmount}, required {requiredAmount}"); } var estimatedSigSize = EstimateSigSize(usedOutputs); tx = currency.CreateHtlcP2PkhSwapPaymentTx( unspentOutputs: usedOutputs, aliceRefundAddress: refundAddress, bobAddress: toAddress, lockTime: lockTime, secretHash: secretHash, secretSize: secretSize, amount: orderAmount, fee: fee); var txSize = tx.VirtualSize(); fee = (long)(currency.FeeRate * (txSize + estimatedSigSize)); requiredAmount = orderAmount + fee; } while (usedAmount < requiredAmount); tx = currency.CreateHtlcP2PkhSwapPaymentTx( unspentOutputs: usedOutputs, aliceRefundAddress: refundAddress, bobAddress: toAddress, lockTime: lockTime, secretHash: secretHash, secretSize: secretSize, amount: orderAmount, fee: fee); return(tx); }