public async Task <IBitcoinBasedTransaction> CreateSwapRefundTxAsync(
            IBitcoinBasedTransaction paymentTx,
            long amount,
            string refundAddress,
            byte[] redeemScript,
            DateTimeOffset lockTime,
            BitcoinBasedConfig currencyConfig)
        {
            var swapOutput = paymentTx.Outputs
                             .Cast <BitcoinBasedTxOutput>()
                             .FirstOrDefault(o => o.IsPayToScriptHash(redeemScript));

            if (swapOutput == null)
            {
                throw new Exception("Can't find pay to script hash output");
            }

            var feeRate = await currencyConfig
                          .GetFeeRateAsync()
                          .ConfigureAwait(false);

            var inputSize = new BitcoinInputToSign
            {
                Output = swapOutput,
                Signer = new BitcoinRefundSigner()
            }.SizeWithSignature();

            var outputSize = new BitcoinDestination
            {
                AmountInSatoshi = amount,
                Script          = BitcoinAddress.Create(refundAddress, currencyConfig.Network).ScriptPubKey
            }.Size();

            var(txSize, _) = BitcoinTransactionParams.CalculateTxSize(
                inputsCount: 1,
                inputsSize: inputSize,
                outputsCount: 1,
                outputsSize: outputSize,
                witnessCount: swapOutput.IsSegWit ? 1 : 0,
                changeOutputSize: 0);

            var feeInSatoshi = (long)(txSize * feeRate);

            if (amount - feeInSatoshi < 0)
            {
                throw new Exception($"Insufficient funds for fee. Available {amount}, required {feeInSatoshi}");
            }

            return(currencyConfig.CreateP2PkhTx(
                       unspentOutputs: new ITxOutput[] { swapOutput },
                       destinationAddress: refundAddress,
                       changeAddress: refundAddress,
                       amount: amount - feeInSatoshi,
                       fee: feeInSatoshi,
                       lockTime: lockTime,
                       knownRedeems: new Script(redeemScript)));
        }
        public async Task <IBitcoinBasedTransaction> CreateSwapPaymentTxAsync(
            IEnumerable <BitcoinBasedTxOutput> fromOutputs,
            long amount,
            string refundAddress,
            string toAddress,
            DateTimeOffset lockTime,
            byte[] secretHash,
            int secretSize,
            BitcoinBasedConfig currencyConfig,
            CancellationToken cancellationToken = default)
        {
            var availableAmountInSatoshi = fromOutputs.Sum(o => o.Value);

            if (availableAmountInSatoshi <= amount)
            {
                throw new Exception($"Insufficient funds. Available {fromOutputs.Sum(o => o.Value)}, required: {amount}");
            }

            var feeRate = await currencyConfig
                          .GetFeeRateAsync()
                          .ConfigureAwait(false);

            var lockScript = BitcoinBasedSwapTemplate.GenerateHtlcP2PkhSwapPayment(
                aliceRefundAddress: refundAddress,
                bobAddress: toAddress,
                lockTimeStamp: lockTime.ToUnixTimeSeconds(),
                secretHash: secretHash,
                secretSize: secretSize,
                expectedNetwork: currencyConfig.Network);

            var feeInSatoshi = 0L;

            var inputsToSign = fromOutputs
                               .Select(o => new BitcoinInputToSign {
                Output = o
            });

            var destinations = new BitcoinDestination[]
            {
                new BitcoinDestination
                {
                    Script          = lockScript.PaymentScript,
                    AmountInSatoshi = amount
                }
            };

            var txParams = await BitcoinTransactionParams.SelectTransactionParamsByFeeRateAsync(
                availableInputs : inputsToSign,
                destinations : destinations,
                changeAddress : refundAddress,
                feeRate : feeRate,
                currencyConfig : currencyConfig,
                cancellationToken : cancellationToken)
                           .ConfigureAwait(false);

            if (txParams == null) // can't create tx with required feeRate => let's try to change feeRate and try again
            {
                var maxFeeInSatoshi = availableAmountInSatoshi - amount;

                var(txSize, txSizeWithChange) = BitcoinTransactionParams.CalculateTxSize(
                    inputsCount: fromOutputs.Count(),
                    inputsSize: inputsToSign.Sum(i => i.SizeWithSignature()),
                    outputsCount: destinations.Length,
                    outputsSize: destinations.Sum(d => d.Size()),
                    witnessCount: fromOutputs.Sum(o => o.IsSegWit ? 1 : 0),
                    changeOutputSize: BitcoinTransactionParams.CalculateChangeOutputSize(
                        changeAddress: refundAddress,
                        network: currencyConfig.Network));

                var estimatedFeeRate = maxFeeInSatoshi / txSizeWithChange;

                if (Math.Abs(feeRate - estimatedFeeRate) / feeRate > MaxFeeRateChangePercent)
                {
                    throw new Exception($"Insufficient funds. Available {fromOutputs.Sum(o => o.Value)}, required: {amount}. Probably feeRate has changed a lot.");
                }

                feeInSatoshi = maxFeeInSatoshi;
            }
            else
            {
                feeInSatoshi = (long)txParams.FeeInSatoshi;
            }

            var tx = currencyConfig
                     .CreateHtlcP2PkhScriptSwapPaymentTx(
                unspentOutputs: txParams?.InputsToSign.Select(i => i.Output) ?? fromOutputs,
                aliceRefundAddress: refundAddress,
                bobAddress: toAddress,
                lockTime: lockTime,
                secretHash: secretHash,
                secretSize: secretSize,
                amount: amount,
                fee: feeInSatoshi,
                redeemScript: out _);

            return(tx);
        }