Example #1
0
        /// <summary>
        /// Constructs an unsigned transaction by referencing previous unspent outputs.
        /// A change output is added when necessary to return extra value back to the wallet.
        /// </summary>
        /// <param name="outputs">Transaction output array without change.</param>
        /// <param name="changeScript">Output script to pay change to.</param>
        /// <param name="fetchInputsAsync">Input selection source.</param>
        /// <returns>Unsigned transaction and total input amount.</returns>
        /// <exception cref="InsufficientFundsException">Input source was unable to provide enough input value.</exception>
        public static async Task <Tuple <Transaction, Amount> > BuildUnsignedTransaction(Transaction.Output[] outputs,
                                                                                         OutputScript changeScript,
                                                                                         Amount feePerKb,
                                                                                         InputSource fetchInputsAsync)
        {
            if (outputs == null)
            {
                throw new ArgumentNullException(nameof(outputs));
            }
            if (changeScript == null)
            {
                throw new ArgumentNullException(nameof(changeScript));
            }
            if (fetchInputsAsync == null)
            {
                throw new ArgumentNullException(nameof(fetchInputsAsync));
            }

            var targetAmount  = outputs.Sum(o => o.Amount);
            var estimatedSize = Transaction.EstimateSerializeSize(1, outputs, true);
            var targetFee     = TransactionFees.FeeForSerializeSize(feePerKb, estimatedSize);

            while (true)
            {
                var funding = await fetchInputsAsync(targetAmount + targetFee);

                var inputAmount = funding.Item1;
                var inputs      = funding.Item2;
                if (inputAmount < targetAmount + targetFee)
                {
                    throw new InsufficientFundsException();
                }

                var unsignedTransaction = new Transaction(Transaction.SupportedVersion, inputs, outputs, 0, 0);
                if (inputAmount > targetAmount + targetFee)
                {
                    unsignedTransaction = TransactionFees.AddChange(unsignedTransaction, inputAmount,
                                                                    changeScript, feePerKb);
                }

                if (TransactionFees.EstimatedFeePerKb(unsignedTransaction, inputAmount) < feePerKb)
                {
                    estimatedSize = Transaction.EstimateSerializeSize(inputs.Length, outputs, true);
                    targetFee     = TransactionFees.FeeForSerializeSize(feePerKb, estimatedSize);
                }
                else
                {
                    return(Tuple.Create(unsignedTransaction, inputAmount));
                }
            }
        }
Example #2
0
        /// <summary>
        /// Constructs an unsigned transaction by referencing previous unspent outputs.
        /// A change output is added when necessary to return extra value back to the wallet.
        /// </summary>
        /// <param name="outputs">Transaction output array without change.</param>
        /// <param name="changeScript">Output script to pay change to.</param>
        /// <param name="fetchInputsAsync">Input selection source.</param>
        /// <returns>Unsigned transaction and total input amount.</returns>
        /// <exception cref="InsufficientFundsException">Input source was unable to provide enough input value.</exception>
        public static async Task <Tuple <Transaction, Amount> > BuildUnsignedTransaction(Transaction.Output[] outputs,
                                                                                         Amount feePerKb,
                                                                                         InputSource fetchInputsAsync,
                                                                                         ChangeSource fetchChangeAsync)
        {
            if (outputs == null)
            {
                throw new ArgumentNullException(nameof(outputs));
            }
            if (fetchInputsAsync == null)
            {
                throw new ArgumentNullException(nameof(fetchInputsAsync));
            }
            if (fetchChangeAsync == null)
            {
                throw new ArgumentNullException(nameof(fetchChangeAsync));
            }

            var targetAmount  = outputs.Sum(o => o.Amount);
            var estimatedSize = Transaction.EstimateSerializeSize(1, outputs, true);
            var targetFee     = TransactionFees.FeeForSerializeSize(feePerKb, estimatedSize);

            while (true)
            {
                var funding = await fetchInputsAsync(targetAmount + targetFee);

                var inputAmount = funding.Item1;
                var inputs      = funding.Item2;
                if (inputAmount < targetAmount + targetFee)
                {
                    throw new InsufficientFundsException();
                }

                var maxSignedSize   = Transaction.EstimateSerializeSize(inputs.Length, outputs, true);
                var maxRequiredFee  = TransactionFees.FeeForSerializeSize(feePerKb, maxSignedSize);
                var remainingAmount = inputAmount - targetAmount;
                if (remainingAmount < maxRequiredFee)
                {
                    targetFee = maxRequiredFee;
                    continue;
                }

                var unsignedTransaction = new Transaction(Transaction.SupportedVersion, inputs, outputs, 0, 0);
                var changeAmount        = inputAmount - targetAmount - maxRequiredFee;
                if (changeAmount != 0 && !TransactionRules.IsDustAmount(changeAmount, Transaction.PayToPubKeyHashPkScriptSize, feePerKb))
                {
                    var changeScript = await fetchChangeAsync();

                    if (changeScript.Script.Length > Transaction.PayToPubKeyHashPkScriptSize)
                    {
                        throw new Exception("Fee estimation requires change scripts no larger than P2PKH output scripts");
                    }
                    var changeOutput = new Transaction.Output(changeAmount, Transaction.Output.LatestPkScriptVersion, changeScript.Script);

                    var outputList = unsignedTransaction.Outputs.ToList();
                    outputList.Add(changeOutput);
                    var outputsWithChange = outputList.ToArray();

                    unsignedTransaction = new Transaction(unsignedTransaction.Version, unsignedTransaction.Inputs, outputsWithChange,
                                                          unsignedTransaction.LockTime, unsignedTransaction.Expiry);
                }

                return(Tuple.Create(unsignedTransaction, inputAmount));
            }
        }