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()); }
public async Task GetFeesAsync(NetworkType networkType) { using (var client = new WasabiClient(LiveServerTestsFixture.UriMappings[networkType])) { var feeEstimationPairs = await client.GetFeesAsync(1000); Assert.True(feeEstimationPairs.NotNullAndNotEmpty()); } }
/// <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];