Esempio n. 1
0
        /// <summary>
        /// Add recipients to the <see cref="TransactionBuilder"/>.
        /// </summary>
        /// <param name="context">The context associated with the current transaction being built.</param>
        /// <remarks>
        /// Add outputs to the <see cref="TransactionBuilder"/> based on the <see cref="Recipient"/> list.
        /// </remarks>
        protected virtual void AddRecipients(TransactionBuildContext context)
        {
            if (context.Recipients.Any(a => a.Amount == Money.Zero))
            {
                throw new WalletException("No amount specified.");
            }

            if (context.Recipients.Any(a => a.SubtractFeeFromAmount))
            {
                throw new NotImplementedException("Substracting the fee from the recipient is not supported yet.");
            }

            foreach (Recipient recipient in context.Recipients)
            {
                context.TransactionBuilder.Send(recipient.ScriptPubKey, recipient.Amount);
            }
        }
Esempio n. 2
0
        /// <summary>
        /// Find the next available change address.
        /// </summary>
        /// <param name="context">The context associated with the current transaction being built.</param>
        protected void FindChangeAddress(TransactionBuildContext context)
        {
            if (context.ChangeAddress == null)
            {
                // If no change address is supplied, get a new address to send the change to.
                context.ChangeAddress = this.walletManager.GetUnusedChangeAddress(new WalletAccountReference(context.AccountReference.WalletName, context.AccountReference.AccountName));
            }

            if (context.UseSegwitChangeAddress)
            {
                context.TransactionBuilder.SetChange(new BitcoinWitPubKeyAddress(context.ChangeAddress.Bech32Address, this.network).ScriptPubKey);
            }
            else
            {
                context.TransactionBuilder.SetChange(context.ChangeAddress.ScriptPubKey);
            }
        }
Esempio n. 3
0
        /// <inheritdoc />
        public Transaction BuildTransaction(TransactionBuildContext context)
        {
            this.InitializeTransactionBuilder(context);

            const int maxRetries = 5;
            int       retryCount = 0;

            TransactionPolicyError[] errors = null;
            while (retryCount <= maxRetries)
            {
                if (context.Shuffle)
                {
                    context.TransactionBuilder.Shuffle();
                }

                Transaction transaction = context.TransactionBuilder.BuildTransaction(false);
                if (context.Sign)
                {
                    ICoin[] coinsSpent = context.TransactionBuilder.FindSpentCoins(transaction);
                    // TODO: Improve this as we already have secrets when running a retry iteration.
                    this.AddSecrets(context, coinsSpent);
                    context.TransactionBuilder.SignTransactionInPlace(transaction);
                }

                if (context.TransactionBuilder.Verify(transaction, out errors))
                {
                    return(transaction);
                }

                // Retry only if error is of type 'FeeTooLowPolicyError'
                if (!errors.Any(e => e is FeeTooLowPolicyError))
                {
                    break;
                }

                retryCount++;
            }

            string errorsMessage = string.Join(" - ", errors.Select(s => s.ToString()));

            this.logger.LogError($"Build transaction failed: {errorsMessage}");
            throw new WalletException($"Could not build the transaction. Details: {errorsMessage}");
        }
Esempio n. 4
0
        /// <summary>
        /// Find all available outputs (UTXO's) that belong to <see cref="WalletAccountReference.AccountName"/>.
        /// Then add them to the <see cref="TransactionBuildContext.UnspentOutputs"/>.
        /// </summary>
        /// <param name="context">The context associated with the current transaction being built.</param>
        protected void AddCoins(TransactionBuildContext context)
        {
            context.UnspentOutputs = this.walletManager.GetSpendableTransactionsInAccount(context.AccountReference, context.MinConfirmations).ToList();

            if (context.UnspentOutputs.Count == 0)
            {
                throw new WalletException("No spendable transactions found.");
            }

            // Get total spendable balance in the account.
            long balance     = context.UnspentOutputs.Sum(t => t.Transaction.Amount);
            long totalToSend = context.Recipients.Sum(s => s.Amount);

            if (balance < totalToSend)
            {
                throw new WalletException("Not enough funds.");
            }

            Money sum   = 0;
            var   coins = new List <Coin>();

            if (context.SelectedInputs != null && context.SelectedInputs.Any())
            {
                // 'SelectedInputs' are inputs that must be included in the
                // current transaction. At this point we check the given
                // input is part of the UTXO set and filter out UTXOs that are not
                // in the initial list if 'context.AllowOtherInputs' is false.

                Dictionary <OutPoint, UnspentOutputReference> availableHashList = context.UnspentOutputs.ToDictionary(item => item.ToOutPoint(), item => item);

                if (!context.SelectedInputs.All(input => availableHashList.ContainsKey(input)))
                {
                    throw new WalletException("Not all the selected inputs were found on the wallet.");
                }

                if (!context.AllowOtherInputs)
                {
                    foreach (KeyValuePair <OutPoint, UnspentOutputReference> unspentOutputsItem in availableHashList)
                    {
                        if (!context.SelectedInputs.Contains(unspentOutputsItem.Key))
                        {
                            context.UnspentOutputs.Remove(unspentOutputsItem.Value);
                        }
                    }
                }

                foreach (OutPoint outPoint in context.SelectedInputs)
                {
                    UnspentOutputReference item = availableHashList[outPoint];

                    coins.Add(new Coin(item.Transaction.Id, (uint)item.Transaction.Index, item.Transaction.Amount, item.Transaction.ScriptPubKey));
                    sum += item.Transaction.Amount;
                }
            }

            foreach (UnspentOutputReference item in context.UnspentOutputs
                     .OrderByDescending(a => a.Confirmations > 0)
                     .ThenByDescending(a => a.Transaction.Amount))
            {
                if (context.SelectedInputs?.Contains(item.ToOutPoint()) ?? false)
                {
                    continue;
                }

                // If the total value is above the target
                // then it's safe to stop adding UTXOs to the coin list.
                // The primary goal is to reduce the time it takes to build a trx
                // when the wallet is bloated with UTXOs.

                // Get to our total, and then check that we're a little bit over to account for tx fees.
                // If it gets over totalToSend but doesn't hit this break, that's fine too.
                // The TransactionBuilder will have a go with what we give it, and throw NotEnoughFundsException accurately if it needs to.
                if (sum > totalToSend + PretendMaxFee)
                {
                    break;
                }

                coins.Add(new Coin(item.Transaction.Id, (uint)item.Transaction.Index, item.Transaction.Amount, item.Transaction.ScriptPubKey));
                sum += item.Transaction.Amount;
            }

            // All the UTXOs are added to the builder without filtering.
            // The builder then has its own coin selection mechanism
            // to select the best UTXO set for the corresponding amount.
            // To add a custom implementation of a coin selection override
            // the builder using builder.SetCoinSelection().

            context.TransactionBuilder.AddCoins(coins);
        }
Esempio n. 5
0
        /// <inheritdoc />
        public Money EstimateFee(TransactionBuildContext context)
        {
            this.InitializeTransactionBuilder(context);

            return(context.TransactionFee);
        }