示例#1
0
        public void SendAssetWithChange(TransactionBuilder builder, TransactionBuildContext context, List <ColoredCoin> coins, IDestination destination, AssetMoney amount,
                                        IDestination changeDestination)
        {
            if (amount.Quantity <= 0)
            {
                throw new BackendException("Amount can't be less or equal to zero", ErrorCode.BadInputParameter);
            }

            Action throwError = () =>
            {
                throw new BackendException($"The sum of total applicable outputs is less than the required: {amount.Quantity} {amount.Id}.", ErrorCode.NotEnoughAssetAvailable);
            };

            var selectedCoins = OpenAssetsHelper.CoinSelect(coins, amount);

            if (selectedCoins == null)
            {
                throwError();
            }

            var orderedCounts = selectedCoins.Cast <ColoredCoin>().OrderBy(o => o.Amount).ToList();
            var sendAmount    = new AssetMoney(amount.Id);
            var cnt           = 0;

            while (sendAmount < amount && cnt < orderedCounts.Count)
            {
                sendAmount += orderedCounts[cnt].Amount;
                cnt++;
            }

            if (sendAmount < amount)
            {
                throwError();
            }

            builder.AddCoins(orderedCounts.Take(cnt));
            context.AddCoins(orderedCounts.Take(cnt));
            builder.SendAsset(destination, amount);

            if ((sendAmount - amount).Quantity > 0)
            {
                builder.SendAsset(changeDestination, sendAmount - amount);
            }
        }
示例#2
0
        public async Task <decimal> SendWithChange(TransactionBuilder builder, TransactionBuildContext context, List <ICoin> coins, IDestination destination, Money amount, IDestination changeDestination, bool addDust = true)
        {
            if (amount.Satoshi <= 0)
            {
                throw new BackendException("Amount can't be less or equal to zero", ErrorCode.BadInputParameter);
            }

            Action throwError = () =>
            {
                throw new BackendException($"The sum of total applicable outputs is less than the required: {amount.Satoshi} satoshis.", ErrorCode.NotEnoughBitcoinAvailable);
            };

            var selectedCoins = OpenAssetsHelper.CoinSelect(coins, amount);

            if (selectedCoins == null)
            {
                throwError();
            }

            var orderedCoins = selectedCoins.OrderBy(o => o.Amount).ToList();
            var sendAmount   = Money.Zero;
            var cnt          = 0;

            while (sendAmount < amount && cnt < orderedCoins.Count)
            {
                sendAmount += orderedCoins[cnt].TxOut.Value;
                cnt++;
            }
            if (sendAmount < amount)
            {
                throwError();
            }

            context.AddCoins(orderedCoins.Take(cnt));
            builder.AddCoins(orderedCoins.Take(cnt));

            var sent = await Send(builder, context, destination, amount, addDust);

            if (sendAmount - amount > 0)
            {
                await Send(builder, context, changeDestination, sendAmount - amount, addDust);
            }
            return(sent);
        }
示例#3
0
        public Task <CreateMultiCashoutTransactionResult> GetMultipleCashoutTransaction(List <ICashoutRequest> cashoutRequests, Guid transactionId)
        {
            return(Retry.Try(async() =>
            {
                var context = _transactionBuildContextFactory.Create(_connectionParams.Network);
                var assetSetting = await _assetSettingCache.GetItemAsync("BTC");

                var hotWallet = OpenAssetsHelper.ParseAddress(assetSetting.HotWallet);
                var changeWallet = OpenAssetsHelper.ParseAddress(string.IsNullOrWhiteSpace(assetSetting.ChangeWallet)
                    ? assetSetting.HotWallet
                    : assetSetting.ChangeWallet);

                return await context.Build(async() =>
                {
                    var builder = new TransactionBuilder();

                    var hotWalletOutputs = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(hotWallet.ToString())).OfType <Coin>().ToList();

                    var hotWalletBalance = new Money(hotWalletOutputs.Select(o => o.Amount).DefaultIfEmpty().Sum(o => o?.Satoshi ?? 0));

                    var maxFeeForTransaction = Money.FromUnit(0.099M, MoneyUnit.BTC);

                    var selectedCoins = new List <Coin>();

                    var maxInputsCount = maxFeeForTransaction.Satoshi / (await _feeProvider.GetFeeRate()).GetFee(Constants.InputSize).Satoshi;
                    var tryCount = 100;
                    do
                    {
                        if (selectedCoins.Count > maxInputsCount && cashoutRequests.Count > 1)
                        {
                            cashoutRequests = cashoutRequests.Take(cashoutRequests.Count - 1).ToList();
                            selectedCoins.Clear();
                        }
                        else
                        if (selectedCoins.Count > 0)
                        {
                            break;
                        }

                        var totalAmount = Money.FromUnit(cashoutRequests.Select(o => o.Amount).Sum(), MoneyUnit.BTC);

                        if (hotWalletBalance < totalAmount + maxFeeForTransaction)
                        {
                            var changeBalance = Money.Zero;
                            List <Coin> changeWalletOutputs = new List <Coin>();
                            if (hotWallet != changeWallet)
                            {
                                changeWalletOutputs = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(changeWallet.ToString()))
                                                      .OfType <Coin>().ToList();
                                changeBalance = new Money(changeWalletOutputs.Select(o => o.Amount).DefaultIfEmpty().Sum(o => o?.Satoshi ?? 0));
                            }
                            if (changeBalance + hotWalletBalance >= totalAmount + maxFeeForTransaction)
                            {
                                selectedCoins.AddRange(hotWalletOutputs);
                                selectedCoins.AddRange(OpenAssetsHelper
                                                       .CoinSelect(changeWalletOutputs, totalAmount + maxFeeForTransaction - hotWalletBalance).OfType <Coin>());
                            }
                            else
                            {
                                selectedCoins.AddRange(hotWalletOutputs);
                                selectedCoins.AddRange(changeWalletOutputs);

                                int cashoutsUsedCount = 0;
                                var cashoutsAmount = await _transactionBuildHelper.CalcFee(selectedCoins.Count, cashoutRequests.Count + 1);
                                foreach (var cashoutRequest in cashoutRequests)
                                {
                                    cashoutsAmount += Money.FromUnit(cashoutRequest.Amount, MoneyUnit.BTC);
                                    if (cashoutsAmount > hotWalletBalance + changeBalance)
                                    {
                                        break;
                                    }
                                    cashoutsUsedCount++;
                                }
                                if (cashoutsUsedCount == 0)
                                {
                                    throw new BackendException("Not enough bitcoin available", ErrorCode.NotEnoughBitcoinAvailable);
                                }
                                cashoutRequests = cashoutRequests.Take(cashoutsUsedCount).ToList();
                            }

                            if (changeWallet != hotWallet)
                            {
                                if (assetSetting.Asset == Constants.DefaultAssetSetting)
                                {
                                    assetSetting = await CreateAssetSetting("BTC", assetSetting);
                                }

                                if (assetSetting.Asset != Constants.DefaultAssetSetting)
                                {
                                    await _assetSettingRepository.UpdateHotWallet(assetSetting.Asset, assetSetting.ChangeWallet);
                                }
                            }
                        }
                        else
                        {
                            selectedCoins.AddRange(OpenAssetsHelper.CoinSelect(hotWalletOutputs, totalAmount + maxFeeForTransaction).OfType <Coin>());
                        }
                    } while (tryCount-- > 0);

                    var selectedCoinsAmount = new Money(selectedCoins.Sum(o => o.Amount));
                    var sendAmount = new Money(cashoutRequests.Sum(o => o.Amount), MoneyUnit.BTC);

                    builder.AddCoins(selectedCoins);
                    foreach (var cashout in cashoutRequests)
                    {
                        var amount = Money.FromUnit(cashout.Amount, MoneyUnit.BTC);
                        builder.Send(OpenAssetsHelper.ParseAddress(cashout.DestinationAddress), amount);
                    }

                    builder.Send(changeWallet, selectedCoinsAmount - sendAmount);
                    builder.SubtractFees();
                    builder.SendEstimatedFees(await _feeProvider.GetFeeRate());
                    builder.SetChange(changeWallet);

                    var tx = builder.BuildTransaction(true);
                    _transactionBuildHelper.AggregateOutputs(tx);

                    await _broadcastedOutputRepository.InsertOutputs(
                        tx.Outputs.AsCoins().Where(o => o.ScriptPubKey == changeWallet.ScriptPubKey).Select(o =>
                                                                                                            new BroadcastedOutput(o, transactionId, _connectionParams.Network)).ToList());

                    await _spentOutputService.SaveSpentOutputs(transactionId, tx);

                    return new CreateMultiCashoutTransactionResult
                    {
                        Transaction = tx,
                        UsedRequests = cashoutRequests
                    };
                });
            }, exception => (exception as BackendException)?.Code == ErrorCode.TransactionConcurrentInputsProblem, 3, _log));
        }
示例#4
0
        public async Task Test()
        {
            var hotWallet    = OpenAssetsHelper.ParseAddress("mj5FEqrC2P4FjFNfX8q3eZ4UABWUcRNy9r");
            var changeWallet = OpenAssetsHelper.ParseAddress("minog49vnNVuWK5kSs5Ut1iPyNZcR1i7he");

            var hotWalletOutputs = GenerateOutputs(5);

            var hotWalletBalance = new Money(hotWalletOutputs.Select(o => o.Amount).DefaultIfEmpty().Sum(o => o?.Satoshi ?? 0));

            var maxFeeForTransaction = Money.FromUnit(0.099M, MoneyUnit.BTC);

            var selectedCoins = new List <Coin>();

            var _feeProvider            = Config.Services.GetService <IFeeProvider>();
            var _transactionBuildHelper = Config.Services.GetService <ITransactionBuildHelper>();
            var cashoutRequests         = (GenerateCashoutRequest(200)).ToList();

            var maxInputsCount = maxFeeForTransaction.Satoshi / (await _feeProvider.GetFeeRate()).GetFee(Constants.InputSize).Satoshi;

            do
            {
                if (selectedCoins.Count > maxInputsCount && cashoutRequests.Count > 1)
                {
                    cashoutRequests = cashoutRequests.Take(cashoutRequests.Count - 1).ToList();
                    selectedCoins.Clear();
                }
                else
                if (selectedCoins.Count > 0)
                {
                    break;
                }

                var totalAmount = Money.FromUnit(cashoutRequests.Select(o => o.Amount).Sum(), MoneyUnit.BTC);

                if (hotWalletBalance < totalAmount + maxFeeForTransaction)
                {
                    var         changeBalance       = Money.Zero;
                    List <Coin> changeWalletOutputs = new List <Coin>();
                    if (hotWallet != changeWallet)
                    {
                        changeWalletOutputs = GenerateOutputs(1).ToList();
                        changeBalance       = new Money(changeWalletOutputs.Select(o => o.Amount).DefaultIfEmpty().Sum(o => o?.Satoshi ?? 0));
                    }
                    if (changeBalance + hotWalletBalance >= totalAmount + maxFeeForTransaction)
                    {
                        selectedCoins.AddRange(hotWalletOutputs);
                        selectedCoins.AddRange(OpenAssetsHelper
                                               .CoinSelect(changeWalletOutputs, totalAmount + maxFeeForTransaction - hotWalletBalance).OfType <Coin>());
                    }
                    else
                    {
                        selectedCoins.AddRange(hotWalletOutputs);
                        selectedCoins.AddRange(changeWalletOutputs);

                        int cashoutsUsedCount = 0;
                        var cashoutsAmount    = await _transactionBuildHelper.CalcFee(selectedCoins.Count, cashoutRequests.Count + 1);

                        foreach (var cashoutRequest in cashoutRequests)
                        {
                            cashoutsAmount += Money.FromUnit(cashoutRequest.Amount, MoneyUnit.BTC);
                            if (cashoutsAmount > hotWalletBalance + changeBalance)
                            {
                                break;
                            }
                            cashoutsUsedCount++;
                        }
                        if (cashoutsUsedCount == 0)
                        {
                            throw new BackendException("Not enough bitcoin available", ErrorCode.NotEnoughBitcoinAvailable);
                        }
                        cashoutRequests = cashoutRequests.Take(cashoutsUsedCount).ToList();
                    }
                }
                else
                {
                    selectedCoins.AddRange(OpenAssetsHelper.CoinSelect(hotWalletOutputs, totalAmount + maxFeeForTransaction).OfType <Coin>());
                }
            } while (true);

            var selectedCoinsAmount = new Money(selectedCoins.Sum(o => o.Amount));
            var sendAmount          = new Money(cashoutRequests.Sum(o => o.Amount), MoneyUnit.BTC);
            var builder             = new TransactionBuilder();

            builder.AddCoins(selectedCoins);
            foreach (var cashout in cashoutRequests)
            {
                var amount = Money.FromUnit(cashout.Amount, MoneyUnit.BTC);
                builder.Send(OpenAssetsHelper.ParseAddress(cashout.DestinationAddress), amount);
            }

            builder.Send(changeWallet, selectedCoinsAmount - sendAmount);

            builder.SubtractFees();
            builder.SendEstimatedFees(await _feeProvider.GetFeeRate());
            builder.SetChange(changeWallet);

            var tx = builder.BuildTransaction(true);

            _transactionBuildHelper.AggregateOutputs(tx);
        }