Пример #1
0
        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);
        }
Пример #2
0
        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);
        }