Ejemplo n.º 1
0
        public async Task <uint256> SendManyAsync(string fromAccount, string addressesJson, int minConf = 1, string comment = null, string subtractFeeFromJson = null, bool isReplaceable = false, int?confTarget = null, string estimateMode = "UNSET")
        {
            if (string.IsNullOrEmpty(addressesJson))
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_PARAMETER, "No valid output addresses specified.");
            }

            var addresses = new Dictionary <string, decimal>();

            try
            {
                // Outputs addresses are keyvalue pairs of address, amount. Translate to Receipient list.
                addresses = JsonConvert.DeserializeObject <Dictionary <string, decimal> >(addressesJson);
            }
            catch (JsonSerializationException ex)
            {
                throw new RPCServerException(RPCErrorCode.RPC_PARSE_ERROR, ex.Message);
            }

            if (addresses.Count == 0)
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_PARAMETER, "No valid output addresses specified.");
            }

            // Optional list of addresses to subtract fees from.
            IEnumerable <BitcoinAddress> subtractFeeFromAddresses = null;

            if (!string.IsNullOrEmpty(subtractFeeFromJson))
            {
                try
                {
                    subtractFeeFromAddresses = JsonConvert.DeserializeObject <List <string> >(subtractFeeFromJson).Select(i => BitcoinAddress.Create(i, this.fullNode.Network));
                }
                catch (JsonSerializationException ex)
                {
                    throw new RPCServerException(RPCErrorCode.RPC_PARSE_ERROR, ex.Message);
                }
            }

            var recipients = new List <Recipient>();

            foreach (var address in addresses)
            {
                // Check for duplicate recipients
                var recipientAddress = BitcoinAddress.Create(address.Key, this.fullNode.Network).ScriptPubKey;
                if (recipients.Any(r => r.ScriptPubKey == recipientAddress))
                {
                    throw new RPCServerException(RPCErrorCode.RPC_INVALID_PARAMETER, string.Format("Invalid parameter, duplicated address: {0}.", recipientAddress));
                }

                var recipient = new Recipient
                {
                    ScriptPubKey          = recipientAddress,
                    Amount                = Money.Coins(address.Value),
                    SubtractFeeFromAmount = subtractFeeFromAddresses == null ? false : subtractFeeFromAddresses.Contains(BitcoinAddress.Create(address.Key, this.fullNode.Network))
                };

                recipients.Add(recipient);
            }

            WalletAccountReference accountReference = this.GetAccount();

            var context = new TransactionBuildContext(this.fullNode.Network)
            {
                AccountReference = accountReference,
                MinConfirmations = minConf,
                Shuffle          = true, // We shuffle transaction outputs by default as it's better for anonymity.
                Recipients       = recipients,
                CacheSecret      = false
            };

            // Set fee type for transaction build context.
            context.FeeType = FeeType.Medium;

            if (estimateMode.Equals("ECONOMICAL", StringComparison.InvariantCultureIgnoreCase))
            {
                context.FeeType = FeeType.Low;
            }

            else if (estimateMode.Equals("CONSERVATIVE", StringComparison.InvariantCultureIgnoreCase))
            {
                context.FeeType = FeeType.High;
            }

            try
            {
                // Log warnings for currently unsupported parameters.
                if (!string.IsNullOrEmpty(comment))
                {
                    this.logger.LogWarning("'comment' parameter is currently unsupported. Ignored.");
                }

                if (isReplaceable)
                {
                    this.logger.LogWarning("'replaceable' parameter is currently unsupported. Ignored.");
                }

                if (confTarget != null)
                {
                    this.logger.LogWarning("'conf_target' parameter is currently unsupported. Ignored.");
                }

                Transaction transaction = this.walletTransactionHandler.BuildTransaction(context);
                await this.broadcasterManager.BroadcastTransactionAsync(transaction);

                return(transaction.GetHash());
            }
            catch (SecurityException exception)
            {
                throw new RPCServerException(RPCErrorCode.RPC_WALLET_UNLOCK_NEEDED, exception.Message);
            }
            catch (WalletException exception)
            {
                throw new RPCServerException(RPCErrorCode.RPC_WALLET_ERROR, exception.Message);
            }
            catch (NotImplementedException exception)
            {
                throw new RPCServerException(RPCErrorCode.RPC_MISC_ERROR, exception.Message);
            }
        }
Ejemplo n.º 2
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.");
            }

            int totalSubtractingRecipients = context.Recipients.Count(r => r.SubtractFeeFromAmount);

            // If none of them need the fee subtracted then it's simply a matter of adding the individual recipients to the builder.
            if (totalSubtractingRecipients == 0)
            {
                foreach (Recipient recipient in context.Recipients)
                {
                    context.TransactionBuilder.Send(recipient.ScriptPubKey, recipient.Amount);
                }

                return;
            }

            // If the transaction fee has been explicitly specified, and we have any recipients that require a fee to be subtracted
            // from the amount to be sent, then evenly distribute the chosen fee among all recipients. Any remaining fee should be
            // subtracted from the first recipient.
            if (context.TransactionFee != null)
            {
                Money fee          = context.TransactionFee;
                long  recipientFee = fee.Satoshi / totalSubtractingRecipients;
                long  remainingFee = fee.Satoshi % totalSubtractingRecipients;

                for (int i = 0; i < context.Recipients.Count; i++)
                {
                    Recipient recipient = context.Recipients[i];

                    if (recipient.SubtractFeeFromAmount)
                    {
                        // First receiver pays the remainder not divisible by output count.
                        long feeToSubtract   = i == 0 ? remainingFee + recipientFee : recipientFee;
                        long remainingAmount = recipient.Amount.Satoshi - feeToSubtract;
                        if (remainingAmount <= 0)
                        {
                            throw new WalletException($"Fee {feeToSubtract} is higher than amount {recipient.Amount.Satoshi} to send.");
                        }

                        recipient.Amount = new Money(remainingAmount);
                    }

                    context.TransactionBuilder.Send(recipient.ScriptPubKey, recipient.Amount);
                }
            }
            else
            {
                // This is currently a limitation of the NBitcoin TransactionBuilder.
                // The only alternative would possibly be to recompute the output sizes after the AddFee call.
                if (totalSubtractingRecipients > 1)
                {
                    throw new WalletException($"Cannot subtract fee from more than 1 recipient if {nameof(context.TransactionFee)} is not set.");
                }

                // If the transaction fee has not been explicitly specified yet, then the builder needs to assign it later from the wallet fee policy.
                // So we just need to indicate to the builder that the fees must be subtracted from the specified recipient.
                foreach (Recipient recipient in context.Recipients)
                {
                    context.TransactionBuilder.Send(recipient.ScriptPubKey, recipient.Amount);

                    if (recipient.SubtractFeeFromAmount)
                    {
                        context.TransactionBuilder.SubtractFees();
                    }
                }
            }
        }