Add more send tests
MODIFY
MagicalCryptoWallet.Tests/RegTests.cs
MagicalCryptoWallet.Tests/RegTests.cs
@@ -672,9 +672,9 @@ namespace MagicalCryptoWallet.Tests
var res2 = await wallet.BuildTransactionAsync("password", new[] { (scp, new Money(0.05m, MoneyUnit.BTC)) }, 5, false);
Assert.NotNull(res2.Transaction);
- Assert.Single(res2.ExternalOutputs);
- Assert.Equal(scp, res2.ExternalOutputs.Single().ScriptPubKey);
- Assert.Single(res2.InternalOutputs);
+ Assert.Single(res2.OuterWalletOutputs);
+ Assert.Equal(scp, res2.OuterWalletOutputs.Single().ScriptPubKey);
+ Assert.Single(res2.InnerWalletOutputs);
Assert.True(res2.Fee > new Money(5 * 100)); // since there is a sanity check of 5sat/b in the server
Assert.InRange(res2.FeePercentOfSent, 0, 1);
Assert.Single(res2.SpentCoins);
@@ -684,27 +684,30 @@ namespace MagicalCryptoWallet.Tests
await wallet.SendTransactionAsync(res2.Transaction);
- Assert.Contains(res2.InternalOutputs.Single(), wallet.Coins);
+ Assert.Contains(res2.InnerWalletOutputs.Single(), wallet.Coins);
#region Basic
Script receive = wallet.GetReceiveKey("basic1").GetP2wpkhScript();
-
Money amountToSend = wallet.Coins.Where(x => x.Unspent).Sum(x => x.Amount) / 2;
var res = await wallet.BuildTransactionAsync("password", new[] { (receive, amountToSend) }, 1008, allowUnconfirmed: true);
- Assert.Equal(receive, res.ExternalOutputs.Single().ScriptPubKey);
- Assert.Equal(amountToSend, res.ExternalOutputs.Single().Amount);
- if (res.SpentCoins.Sum(x => x.Amount) - res.ExternalOutputs.Sum(x=>x.Amount) == res.Fee) // this happens when change is too small
+ Assert.Equal(2, res.InnerWalletOutputs.Count());
+ Assert.Empty(res.OuterWalletOutputs);
+ var activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
+ var changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive);
+
+ Assert.Equal(receive, activeOutput.ScriptPubKey);
+ Assert.Equal(amountToSend, activeOutput.Amount);
+ if (res.SpentCoins.Sum(x => x.Amount) - activeOutput.Amount == res.Fee) // this happens when change is too small
{
- Assert.NotEmpty(res.InternalOutputs);
- Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == res.ExternalOutputs.Single().Amount);
- Logger.LogInfo<RegTests>($"Change Output: {res.InternalOutputs.Single().Amount.ToString(false, true)} {res.InternalOutputs.Single().ScriptPubKey.GetDestinationAddress(network)}");
+ Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == activeOutput.Amount);
+ Logger.LogInfo<RegTests>($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}");
}
Logger.LogInfo<RegTests>($"Fee: {res.Fee}");
Logger.LogInfo<RegTests>($"FeePercentOfSent: {res.FeePercentOfSent} %");
Logger.LogInfo<RegTests>($"SpendsUnconfirmed: {res.SpendsUnconfirmed}");
- Logger.LogInfo<RegTests>($"Active Output: {res.ExternalOutputs.Single().Amount.ToString(false, true)} {res.ExternalOutputs.Single().ScriptPubKey.GetDestinationAddress(network)}");
+ Logger.LogInfo<RegTests>($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
Logger.LogInfo<RegTests>($"TxId: {res.Transaction.GetHash()}");
var foundReceive = false;
@@ -721,9 +724,40 @@ namespace MagicalCryptoWallet.Tests
await wallet.SendTransactionAsync(res.Transaction);
- receive = wallet.GetReceiveKey("basic2").GetP2wpkhScript();
+ #endregion
+
+ #region SubtractFeeFromAmount
+ receive = wallet.GetReceiveKey("basic2").GetP2wpkhScript();
amountToSend = wallet.Coins.Where(x => x.Unspent).Sum(x => x.Amount) / 2;
+ res = await wallet.BuildTransactionAsync("password", new[] { (receive, amountToSend) }, 1008, allowUnconfirmed: true, subtractFeeFromAmountIndex: 0);
+
+ Assert.Equal(2, res.InnerWalletOutputs.Count());
+ Assert.Empty(res.OuterWalletOutputs);
+ activeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey == receive);
+ changeOutput = res.InnerWalletOutputs.Single(x => x.ScriptPubKey != receive);
+
+ Assert.Equal(receive, activeOutput.ScriptPubKey);
+ Assert.Equal(amountToSend - res.Fee, activeOutput.Amount);
+ Assert.Contains(res.Transaction.Transaction.Outputs, x => x.Value == changeOutput.Amount);
+ Logger.LogInfo<RegTests>($"Fee: {res.Fee}");
+ Logger.LogInfo<RegTests>($"FeePercentOfSent: {res.FeePercentOfSent} %");
+ Logger.LogInfo<RegTests>($"SpendsUnconfirmed: {res.SpendsUnconfirmed}");
+ Logger.LogInfo<RegTests>($"Active Output: {activeOutput.Amount.ToString(false, true)} {activeOutput.ScriptPubKey.GetDestinationAddress(network)}");
+ Logger.LogInfo<RegTests>($"Change Output: {changeOutput.Amount.ToString(false, true)} {changeOutput.ScriptPubKey.GetDestinationAddress(network)}");
+ Logger.LogInfo<RegTests>($"TxId: {res.Transaction.GetHash()}");
+
+ foundReceive = false;
+ Assert.InRange(res.Transaction.Transaction.Outputs.Count, 1, 2);
+ foreach (var output in res.Transaction.Transaction.Outputs)
+ {
+ if (output.ScriptPubKey == receive)
+ {
+ foundReceive = true;
+ Assert.Equal(amountToSend - res.Fee, output.Value);
+ }
+ }
+ Assert.True(foundReceive);
#endregion
}

MODIFY
MagicalCryptoWallet/Models/BuildTransactionResult.cs
MagicalCryptoWallet/Models/BuildTransactionResult.cs
@@ -12,18 +12,18 @@ namespace MagicalCryptoWallet.Models
public bool SpendsUnconfirmed { get; }
public Money Fee { get; }
public decimal FeePercentOfSent { get; }
- public IEnumerable<SmartCoin> ExternalOutputs { get; }
- public IEnumerable<SmartCoin> InternalOutputs { get; }
+ public IEnumerable<SmartCoin> OuterWalletOutputs { get; }
+ public IEnumerable<SmartCoin> InnerWalletOutputs { get; }
public IEnumerable<SmartCoin> SpentCoins { get; }
- public BuildTransactionResult(SmartTransaction transaction, bool spendsUnconfirmed, Money fee, decimal feePercentOfSent, IEnumerable<SmartCoin> externalOutputs, IEnumerable<SmartCoin> internalOutputs, IEnumerable<SmartCoin> spentCoins)
+ public BuildTransactionResult(SmartTransaction transaction, bool spendsUnconfirmed, Money fee, decimal feePercentOfSent, IEnumerable<SmartCoin> outerWalletOutputs, IEnumerable<SmartCoin> innerWalletOutputs, IEnumerable<SmartCoin> spentCoins)
{
Transaction = Guard.NotNull(nameof(transaction), transaction);
SpendsUnconfirmed = spendsUnconfirmed;
Fee = fee ?? Money.Zero;
FeePercentOfSent = feePercentOfSent;
- ExternalOutputs = externalOutputs ?? new List<SmartCoin>();
- InternalOutputs = internalOutputs ?? new List<SmartCoin>();
+ OuterWalletOutputs = outerWalletOutputs ?? new List<SmartCoin>();
+ InnerWalletOutputs = innerWalletOutputs ?? new List<SmartCoin>();
SpentCoins = Guard.NotNullOrEmpty(nameof(spentCoins), spentCoins);
}
}

MODIFY
MagicalCryptoWallet/Services/WalletService.cs
MagicalCryptoWallet/Services/WalletService.cs
@@ -577,10 +577,6 @@ namespace MagicalCryptoWallet.Services
{
inNum = allowedSmartCoinInputs.Count();
}
- else if (subtractFeeFromAmountIndex != null)
- {
- inNum = SelectCoinsToSpend(allowedSmartCoinInputs, toSend.Sum(x => x.amount)).Count();
- }
else
{
int expectedMinTxSize = 1 * Constants.P2wpkhInputSizeInBytes + 1 * Constants.OutputSizeInBytes + 10;
@@ -599,37 +595,40 @@ namespace MagicalCryptoWallet.Services
// 5. How much to spend?
long toSendAmountSumInSatoshis = toSend.Sum(x => x.amount); // Does it work if I simply go with Money class here? Is that copied by reference of value?
- var realToSend = toSend.ToList();
- for (int i = 0; i < realToSend.Count; i++)
+ var realToSend = new (Script script, Money amount)[toSend.Length];
+ for (int i = 0; i < toSend.Length; i++) // clone
{
- var item = realToSend[i];
- if (item.amount == Money.Zero)
+ realToSend[i] = (
+ new Script(toSend[i].script.ToString()),
+ new Money(toSend[i].amount.Satoshi));
+ }
+ for (int i = 0; i < realToSend.Length; i++)
{
- item.amount = spendableConfirmedAmount;
+ if (realToSend[i].amount == Money.Zero)
+ {
+ realToSend[i].amount = spendableConfirmedAmount;
if (allowUnconfirmed)
{
- item.amount += spendableUnconfirmedAmount;
+ realToSend[i].amount += spendableUnconfirmedAmount;
}
if (subtractFeeFromAmountIndex == null)
{
- item.amount -= fee;
+ realToSend[i].amount -= fee;
}
- item.amount -= new Money(toSendAmountSumInSatoshis);
+ realToSend[i].amount -= new Money(toSendAmountSumInSatoshis);
}
if (subtractFeeFromAmountIndex == i)
{
- item.amount -= fee;
+ realToSend[i].amount -= fee;
}
- if (item.amount < Money.Zero)
+ if (realToSend[i].amount <= Money.Zero)
{
throw new InsufficientBalanceException();
}
}
- realToSend.RemoveAll(x => x.amount == Money.Zero);
-
// 6. Do some checks
Money totalOutgoingAmount = realToSend.Sum(x => x.amount) + fee;
decimal feePc = (100 * fee.ToDecimal(MoneyUnit.BTC)) / totalOutgoingAmount.ToDecimal(MoneyUnit.BTC);
@@ -684,25 +683,25 @@ namespace MagicalCryptoWallet.Services
TxoRef[] spentOutputs = spentCoins.Select(x => new TxoRef(x.TransactionId, x.Index)).ToArray();
- var externalOutputs = new List<SmartCoin>();
- var internalOutputs = new List<SmartCoin>();
+ var outerWalletOutputs = new List<SmartCoin>();
+ var innerWalletOutputs = new List<SmartCoin>();
for (var i = 0; i < tx.Outputs.Count; i++)
{
TxOut output = tx.Outputs[i];
var coin = new SmartCoin(tx.GetHash(), i, output.ScriptPubKey, output.Value, spentOutputs, Height.Unknown);
- if (KeyManager.GetKeys(KeyState.Clean, isInternal: true).Select(x => x.GetP2wpkhScript()).Contains(coin.ScriptPubKey))
+ if (KeyManager.GetKeys(KeyState.Clean).Select(x => x.GetP2wpkhScript()).Contains(coin.ScriptPubKey))
{
- internalOutputs.Add(coin);
+ innerWalletOutputs.Add(coin);
}
else
{
- externalOutputs.Add(coin);
+ outerWalletOutputs.Add(coin);
}
}
Logger.LogInfo<WalletService>($"Transaction is successfully built: {tx.GetHash()}.");
- return new BuildTransactionResult(new SmartTransaction(tx, Height.Unknown), spendsUnconfirmed, fee, feePc, externalOutputs, internalOutputs, spentCoins);
+ return new BuildTransactionResult(new SmartTransaction(tx, Height.Unknown), spendsUnconfirmed, fee, feePc, outerWalletOutputs, innerWalletOutputs, spentCoins);
}
private IEnumerable<SmartCoin> SelectCoinsToSpend(IEnumerable<SmartCoin> unspentCoins, Money totalOutAmount)

Add more send tests