コード例 #1
0
        public async Task GetFeesAsync(NetworkType networkType)
        {
            using var client = new WasabiClient(LiveServerTestsFixture.UriMappings[networkType], Global.Instance.TorSocks5Endpoint);
            var feeEstimationPairs = await client.GetFeesAsync(1000);

            Assert.True(feeEstimationPairs.NotNullAndNotEmpty());
        }
コード例 #2
0
        public async Task GetFeesAsync(NetworkType networkType)
        {
            using (var client = new WasabiClient(LiveServerTestsFixture.UriMappings[networkType]))
            {
                var feeEstimationPairs = await client.GetFeesAsync(1000);

                Assert.True(feeEstimationPairs.NotNullAndNotEmpty());
            }
        }
コード例 #3
0
        /// <param name="toSend">If Money.Zero then spends all available amount. Doesn't generate change.</param>
        /// <param name="allowUnconfirmed">Allow to spend unconfirmed transactions, if necessary.</param>
        /// <param name="allowedInputs">Only these inputs allowed to be used to build the transaction. The wallet must know the corresponding private keys.</param>
        /// <param name="subtractFeeFromAmountIndex">If null, fee is substracted from the change. Otherwise it denotes the index in the toSend array.</param>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public async Task <BuildTransactionResult> BuildTransactionAsync(string password,
                                                                         Operation[] toSend,
                                                                         int feeTarget,
                                                                         bool allowUnconfirmed              = false,
                                                                         int?subtractFeeFromAmountIndex     = null,
                                                                         Script customChange                = null,
                                                                         IEnumerable <TxoRef> allowedInputs = null)
        {
            password = password ?? "";             // Correction.
            toSend   = Guard.NotNullOrEmpty(nameof(toSend), toSend);
            if (toSend.Any(x => x == null))
            {
                throw new ArgumentNullException($"{nameof(toSend)} cannot contain null element.");
            }
            if (toSend.Any(x => x.Amount < Money.Zero))
            {
                throw new ArgumentException($"{nameof(toSend)} cannot contain negative element.");
            }
            if (toSend.Any(x => x.Amount != Money.Zero && x.Amount < new Money(0.00001m, MoneyUnit.BTC)))
            {
                throw new InvalidOperationException($"Sanity check failed. One of the output is < 0.00001 BTC.");
            }

            long sum = toSend.Select(x => x.Amount).Sum().Satoshi;

            if (sum < 0 || sum > Constants.MaximumNumberOfSatoshis)
            {
                throw new ArgumentOutOfRangeException($"{nameof(toSend)} sum cannot be smaller than 0 or greater than {Constants.MaximumNumberOfSatoshis}.");
            }

            int spendAllCount = toSend.Count(x => x.Amount == Money.Zero);

            if (spendAllCount > 1)
            {
                throw new ArgumentException($"Only one {nameof(toSend)} element can contain Money.Zero. Money.Zero means add the change to the value of this output.");
            }
            if (spendAllCount == 1 && customChange != null)
            {
                throw new ArgumentException($"{nameof(customChange)} and send all to destination cannot be specified the same time.");
            }
            Guard.InRangeAndNotNull(nameof(feeTarget), feeTarget, 0, 1008); // Allow 0 and 1, and correct later.
            if (feeTarget < 2)                                              // Correct 0 and 1 to 2.
            {
                feeTarget = 2;
            }
            if (subtractFeeFromAmountIndex != null)             // If not null, make sure not out of range. If null fee is substracted from the change.
            {
                if (subtractFeeFromAmountIndex < 0)
                {
                    throw new ArgumentOutOfRangeException($"{nameof(subtractFeeFromAmountIndex)} cannot be smaller than 0.");
                }
                if (subtractFeeFromAmountIndex > toSend.Length - 1)
                {
                    throw new ArgumentOutOfRangeException($"{nameof(subtractFeeFromAmountIndex)} can be maximum {nameof(toSend)}.Length - 1. {nameof(subtractFeeFromAmountIndex)}: {subtractFeeFromAmountIndex}, {nameof(toSend)}.Length - 1: {toSend.Length - 1}.");
                }
            }

            // Get allowed coins to spend.
            List <SmartCoin> allowedSmartCoinInputs; // Inputs those can be used to build the transaction.

            if (allowedInputs != null)               // If allowedInputs are specified then select the coins from them.
            {
                if (!allowedInputs.Any())
                {
                    throw new ArgumentException($"{nameof(allowedInputs)} is not null, but empty.");
                }

                if (allowUnconfirmed)
                {
                    allowedSmartCoinInputs = Coins.Where(x => !x.SpentOrCoinJoinInProcess && allowedInputs.Any(y => y.TransactionId == x.TransactionId && y.Index == x.Index)).ToList();
                }
                else
                {
                    allowedSmartCoinInputs = Coins.Where(x => !x.SpentOrCoinJoinInProcess && x.Confirmed && allowedInputs.Any(y => y.TransactionId == x.TransactionId && y.Index == x.Index)).ToList();
                }
            }
            else
            {
                if (allowUnconfirmed)
                {
                    allowedSmartCoinInputs = Coins.Where(x => !x.SpentOrCoinJoinInProcess).ToList();
                }
                else
                {
                    allowedSmartCoinInputs = Coins.Where(x => !x.SpentOrCoinJoinInProcess && x.Confirmed).ToList();
                }
            }

            // 4. Get and calculate fee
            Logger.LogInfo <WalletService>("Calculating dynamic transaction fee...");

            Money feePerBytes = null;

            using (var client = new WasabiClient(IndexDownloader.WasabiClient.TorClient.DestinationUri, IndexDownloader.WasabiClient.TorClient.TorSocks5EndPoint))
            {
                var fees = await client.GetFeesAsync(feeTarget);

                feePerBytes = new Money(fees.Single().Value.Conservative);
            }

            bool spendAll = spendAllCount == 1;
            int  inNum;

            if (spendAll)
            {
                inNum = allowedSmartCoinInputs.Count;
            }
            else
            {
                int expectedMinTxSize = 1 * Constants.P2wpkhInputSizeInBytes + 1 * Constants.OutputSizeInBytes + 10;
                inNum = SelectCoinsToSpend(allowedSmartCoinInputs, toSend.Select(x => x.Amount).Sum() + feePerBytes * expectedMinTxSize).Count();
            }

            // https://bitcoincore.org/en/segwit_wallet_dev/#transaction-fee-estimation
            // https://bitcoin.stackexchange.com/a/46379/26859
            int outNum     = spendAll ? toSend.Length : toSend.Length + 1;                                         // number of addresses to send + 1 for change
            var origTxSize = inNum * Constants.P2pkhInputSizeInBytes + outNum * Constants.OutputSizeInBytes + 10;
            var newTxSize  = inNum * Constants.P2wpkhInputSizeInBytes + outNum * Constants.OutputSizeInBytes + 10; // BEWARE: This assumes segwit only inputs!
            var vSize      = (int)Math.Ceiling(((3 * newTxSize) + origTxSize) / 4m);

            Logger.LogInfo <WalletService>($"Estimated tx size: {vSize} bytes.");
            Money fee = feePerBytes * vSize;

            Logger.LogInfo <WalletService>($"Fee: {fee.ToString(fplus: false, trimExcessZero: true)}");

            // 5. How much to spend?
            long toSendAmountSumInSatoshis = toSend.Select(x => x.Amount).Sum();             // Does it work if I simply go with Money class here? Is that copied by reference of value?
            var  realToSend = new(Script script, Money amount, string label)[toSend.Length];