Exemple #1
0
        public Task <IBitcoinBasedTransaction> CreateSwapRedeemTxAsync(
            IBitcoinBasedTransaction paymentTx,
            long amount,
            string redeemAddress,
            byte[] redeemScript,
            uint sequenceNumber = 0)
        {
            var currency = (BitcoinBasedCurrency)paymentTx.Currency;

            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 estimatedSigSize = BitcoinBasedCurrency.EstimateSigSize(swapOutput, forRedeem: true);

            var tx = currency.CreateP2PkhTx(
                unspentOutputs: new ITxOutput[] { swapOutput },
                destinationAddress: redeemAddress,
                changeAddress: redeemAddress,
                amount: amount,
                fee: 0,
                lockTime: DateTimeOffset.MinValue);

            // fee = txSize * feeRate without dust, because all coins will be send to one address
            var fee = (long)((tx.VirtualSize() + estimatedSigSize) * currency.FeeRate);

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

            tx = currency.CreateP2PkhTx(
                unspentOutputs: new ITxOutput[] { swapOutput },
                destinationAddress: redeemAddress,
                changeAddress: redeemAddress,
                amount: amount - fee,
                fee: fee,
                lockTime: DateTimeOffset.MinValue);

            if (sequenceNumber > 0)
            {
                tx.SetSequenceNumber(sequenceNumber);
            }

            return(Task.FromResult(tx));
        }
Exemple #2
0
        public async Task <FundsUsageEstimation> EstimateMaxAmountAsync(
            string to,
            BlockchainTransactionType type,
            CancellationToken cancellationToken = default)
        {
            var currency = BtcBasedCurrency;

            var unspentOutputs = (await DataRepository
                                  .GetAvailableOutputsAsync(Currency, currency.OutputType(), currency.TransactionType)
                                  .ConfigureAwait(false))
                                 .ToList();

            if (!unspentOutputs.Any())
            {
                return(null);
            }

            var availableAmountInSatoshi = unspentOutputs.Sum(o => o.Value);
            var estimatedSigSize         = BitcoinBasedCurrency.EstimateSigSize(unspentOutputs);

            var testTx = currency
                         .CreatePaymentTx(
                unspentOutputs: unspentOutputs,
                destinationAddress: currency.TestAddress(),
                changeAddress: currency.TestAddress(),
                amount: availableAmountInSatoshi,
                fee: 0,
                lockTime: DateTimeOffset.MinValue);

            // requiredFee = txSize * feeRate without dust, because all coins must be send to one address
            var requiredFeeInSatoshi = (long)((testTx.VirtualSize() + estimatedSigSize) * currency.FeeRate);

            var amount = currency.SatoshiToCoin(Math.Max(availableAmountInSatoshi - requiredFeeInSatoshi, 0));
            var fee    = currency.SatoshiToCoin(requiredFeeInSatoshi);

            return(new FundsUsageEstimation
            {
                TotalAmount = amount,
                TotalFee = fee,
                UsedAddresses = unspentOutputs.Select(o => new AddressUsageEstimation
                {
                    Address = $"{o.TxId}:{o.Index}",
                    Amount = currency.SatoshiToCoin(o.Value)
                })
            });
        }
Exemple #3
0
        public override async Task <(decimal, decimal, decimal)> EstimateMaxAmountToSendAsync(
            string to,
            BlockchainTransactionType type,
            decimal fee      = 0,
            decimal feePrice = 0,
            bool reserve     = false,
            CancellationToken cancellationToken = default)
        {
            var currency = BtcBasedCurrency;

            var unspentOutputs = (await DataRepository
                                  .GetAvailableOutputsAsync(Currency, currency.OutputType(), currency.TransactionType)
                                  .ConfigureAwait(false))
                                 .ToList();

            if (!unspentOutputs.Any())
            {
                return(0m, 0m, 0m);
            }

            var availableAmountInSatoshi = unspentOutputs.Sum(o => o.Value);
            var estimatedSigSize         = BitcoinBasedCurrency.EstimateSigSize(unspentOutputs);

            var testTx = currency
                         .CreatePaymentTx(
                unspentOutputs: unspentOutputs,
                destinationAddress: currency.TestAddress(),
                changeAddress: currency.TestAddress(),
                amount: availableAmountInSatoshi,
                fee: 0,
                lockTime: DateTimeOffset.MinValue);

            // requiredFee = txSize * feeRate without dust, because all coins must be send to one address
            var requiredFeeInSatoshi = (long)((testTx.VirtualSize() + estimatedSigSize) * currency.FeeRate);

            var amount = currency.SatoshiToCoin(Math.Max(availableAmountInSatoshi - requiredFeeInSatoshi, 0));

            fee = currency.SatoshiToCoin(requiredFeeInSatoshi);

            return(amount, fee, 0m);
        }
Exemple #4
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);
        }
Exemple #5
0
        public async Task <FundsUsageEstimation> EstimateFeeExAsync(
            string to,
            decimal amount,
            BlockchainTransactionType type,
            CancellationToken cancellationToken = default)
        {
            var currency = BtcBasedCurrency;

            var amountInSatoshi = currency.CoinToSatoshi(amount);

            var availableOutputs = (await DataRepository
                                    .GetAvailableOutputsAsync(Currency, currency.OutputType(), currency.TransactionType)
                                    .ConfigureAwait(false))
                                   .ToList();

            if (!availableOutputs.Any())
            {
                return(null); // insufficient funds
            }
            for (var i = 1; i <= availableOutputs.Count; ++i)
            {
                var selectedOutputs = availableOutputs
                                      .Take(i)
                                      .ToArray();

                var estimatedSigSize = BitcoinBasedCurrency.EstimateSigSize(selectedOutputs);

                var selectedInSatoshi = selectedOutputs.Sum(o => o.Value);

                if (selectedInSatoshi < amountInSatoshi) // insufficient funds
                {
                    continue;
                }

                var maxFeeInSatoshi = selectedInSatoshi - amountInSatoshi;

                var estimatedTx = currency
                                  .CreatePaymentTx(
                    unspentOutputs: selectedOutputs,
                    destinationAddress: currency.TestAddress(),
                    changeAddress: currency.TestAddress(),
                    amount: amountInSatoshi,
                    fee: maxFeeInSatoshi,
                    lockTime: DateTimeOffset.MinValue);

                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 - amountInSatoshi - estimatedFeeInSatoshi;

                // if estimated change is dust
                if (estimatedChangeInSatoshi >= 0 && estimatedChangeInSatoshi < currency.GetDust())
                {
                    return(new FundsUsageEstimation
                    {
                        TotalAmount = amount,
                        TotalFee = currency.SatoshiToCoin(estimatedFeeInSatoshi + estimatedChangeInSatoshi),
                        UsedAddresses = selectedOutputs.Select(o => new AddressUsageEstimation
                        {
                            Address = $"{o.TxId}:{o.Index}",
                            Amount = currency.SatoshiToCoin(o.Value)
                        })
                    });
                }

                // if estimated change > dust
                var estimatedFeeWithChangeInSatoshi = (long)(estimatedTxSizeWithChange * currency.FeeRate);

                if (estimatedFeeWithChangeInSatoshi > maxFeeInSatoshi) // insufficient funds
                {
                    continue;
                }

                var esitmatedNewChangeInSatoshi = selectedInSatoshi - amountInSatoshi - estimatedFeeWithChangeInSatoshi;

                // if new estimated change is dust
                if (esitmatedNewChangeInSatoshi >= 0 && esitmatedNewChangeInSatoshi < currency.GetDust())
                {
                    return(new FundsUsageEstimation
                    {
                        TotalAmount = amount,
                        TotalFee = currency.SatoshiToCoin(estimatedFeeWithChangeInSatoshi + esitmatedNewChangeInSatoshi),
                        UsedAddresses = selectedOutputs.Select(o => new AddressUsageEstimation
                        {
                            Address = $"{o.TxId}:{o.Index}",
                            Amount = currency.SatoshiToCoin(o.Value)
                        })
                    });
                }

                // if new estimated change > dust
                return(new FundsUsageEstimation
                {
                    TotalAmount = amount,
                    TotalFee = currency.SatoshiToCoin(estimatedFeeWithChangeInSatoshi),
                    UsedAddresses = selectedOutputs.Select(o => new AddressUsageEstimation
                    {
                        Address = $"{o.TxId}:{o.Index}",
                        Amount = currency.SatoshiToCoin(o.Value)
                    })
                });
            }

            return(null); // insufficient funds
        }
Exemple #6
0
        public async Task <int?> EstimateTxSizeAsync(
            decimal amount,
            decimal fee,
            CancellationToken cancellationToken = default)
        {
            var currency = BtcBasedCurrency;

            var amountInSatoshi = currency.CoinToSatoshi(amount);
            var feeInSatoshi    = currency.CoinToSatoshi(fee);

            var availableOutputs = (await DataRepository
                                    .GetAvailableOutputsAsync(Currency, currency.OutputType(), currency.TransactionType)
                                    .ConfigureAwait(false))
                                   .ToList();

            if (!availableOutputs.Any())
            {
                return(null); // insufficient funds
            }
            for (var i = 1; i <= availableOutputs.Count; ++i)
            {
                var selectedOutputs = availableOutputs
                                      .Take(i)
                                      .ToArray();

                var estimatedSigSize = BitcoinBasedCurrency.EstimateSigSize(selectedOutputs);

                var selectedInSatoshi = selectedOutputs.Sum(o => o.Value);

                if (selectedInSatoshi < amountInSatoshi) // insufficient funds
                {
                    continue;
                }

                var maxFeeInSatoshi = selectedInSatoshi - amountInSatoshi;

                if (maxFeeInSatoshi < fee)
                {
                    continue; // insufficient funds
                }
                //var changeInSatoshi = selectedInSatoshi - amountInSatoshi - feeInSatoshi;

                try
                {
                    var estimatedTx = currency
                                      .CreatePaymentTx(
                        unspentOutputs: selectedOutputs,
                        destinationAddress: currency.TestAddress(),
                        changeAddress: currency.TestAddress(),
                        amount: amountInSatoshi,
                        fee: feeInSatoshi,
                        lockTime: DateTimeOffset.MinValue);

                    var estimatedTxVirtualSize = estimatedTx.VirtualSize();

                    return(estimatedTxVirtualSize + estimatedSigSize);
                }
                catch (Exception)
                {
                    continue;
                }
            }

            return(null); // insufficient funds
        }