/// <summary> /// Find all unspent transaction output of the account /// </summary> public IDictionary <Coin, bool> GetUnspentCoins(SafeAccount account = null) { AssertAccount(account); var unspentCoins = new Dictionary <Coin, bool>(); var trackedScriptPubkeys = GetTrackedScriptPubKeysBySafeAccount(account); // 1. Go through all the transactions and their outputs foreach (SmartTransaction tx in Tracker .TrackedTransactions .Where(x => x.Height != Height.Unknown)) { foreach (var coin in tx.Transaction.Outputs.AsCoins()) { // 2. Check if the coin comes with our account if (trackedScriptPubkeys.Contains(coin.ScriptPubKey)) { // 3. Check if coin is unspent, if so add to our utxoSet if (IsUnspent(coin)) { unspentCoins.Add(coin, tx.Confirmed); } } } } return(unspentCoins); }
public HashSet <Script> GetTrackedScriptPubKeysBySafeAccount(SafeAccount account = null) { var maxTracked = Tracker.TrackedScriptPubKeys.Count; var allPossiblyTrackedAddresses = new HashSet <BitcoinAddress>(); foreach (var address in Safe.GetFirstNAddresses(maxTracked, HdPathType.Receive, account)) { allPossiblyTrackedAddresses.Add(address); } foreach (var address in Safe.GetFirstNAddresses(maxTracked, HdPathType.Change, account)) { allPossiblyTrackedAddresses.Add(address); } foreach (var address in Safe.GetFirstNAddresses(maxTracked, HdPathType.NonHardened, account)) { allPossiblyTrackedAddresses.Add(address); } var actuallyTrackedScriptPubKeys = new HashSet <Script>(); foreach (var address in allPossiblyTrackedAddresses) { if (Tracker.TrackedScriptPubKeys.Any(x => x == address.ScriptPubKey)) { actuallyTrackedScriptPubKeys.Add(address.ScriptPubKey); } } return(actuallyTrackedScriptPubKeys); }
public BaseResponse BuildTransaction(string password, SafeAccount safeAccount, BitcoinAddress address, Money amount, FeeType feeType) { if (password != _password) { throw new InvalidOperationException("Wrong password"); } var result = _walletJob.BuildTransactionAsync(address.ScriptPubKey, amount, feeType, safeAccount, Config.CanSpendUnconfirmed).Result; if (result.Success) { return(new BuildTransactionResponse { SpendsUnconfirmed = result.SpendsUnconfirmed, Fee = result.Fee.ToString(false, true), FeePercentOfSent = result.FeePercentOfSent.ToString("0.##"), Hex = result.Transaction.ToHex(), Transaction = result.Transaction.ToString() }); } else { return(new FailureResponse { Message = result.FailingReason }); } }
public AvailableAmount GetBalance(out IDictionary <Coin, bool> unspentCoins, SafeAccount account = null) { // 1. Find all coins I can spend from the account Debug.WriteLine("Finding all unspent coins..."); unspentCoins = GetUnspentCoins(account); // 2. How much money we can spend? var confirmedAvailableAmount = Money.Zero; var unconfirmedAvailableAmount = Money.Zero; foreach (var elem in unspentCoins) { // Value true if confirmed if (elem.Value) { confirmedAvailableAmount += elem.Key.Amount as Money; } else { unconfirmedAvailableAmount += elem.Key.Amount as Money; } } return(new AvailableAmount { Confirmed = confirmedAvailableAmount, Unconfirmed = unconfirmedAvailableAmount }); }
public async Task CreationTestsAsync() { for (int i = 0; i < 2; i++) { var network = i == 0 ? Network.Main : Network.TestNet; const string path = "Wallets/TestWallet.json"; const string password = "******"; const string recoveredPath = "Wallets/RecoveredTestWallet.json"; if (File.Exists(path)) { File.Delete(path); } if (File.Exists(recoveredPath)) { File.Delete(recoveredPath); } var result = await Safe.CreateAsync(password, path, network); var safe = result.Safe; var mnemonic = result.Mnemonic; var loadedSafe = await Safe.LoadAsync(password, path, network); var wantedCreation = DateTimeOffset.ParseExact("1998-01-01", "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); var recoverdSafe = await Safe.RecoverAsync(mnemonic, password, recoveredPath, network, wantedCreation); var alice = new SafeAccount(3); var bob = new SafeAccount(4); try { foreach (AddressType addressType in Enum.GetValues(typeof(AddressType))) { Assert.Equal(safe.GetScriptPubKey(addressType, 3, account: alice), recoverdSafe.GetScriptPubKey(addressType, 3, account: alice)); Assert.Equal(safe.GetScriptPubKey(addressType, 4, HdPathType.NonHardened, account: alice), recoverdSafe.GetScriptPubKey(addressType, 4, HdPathType.NonHardened, account: alice)); Assert.NotEqual(safe.GetScriptPubKey(addressType, 4, HdPathType.Change, account: alice), recoverdSafe.GetScriptPubKey(addressType, 4, HdPathType.NonHardened, account: alice)); Assert.NotEqual(safe.GetScriptPubKey(addressType, 3, HdPathType.NonHardened, account: alice), recoverdSafe.GetScriptPubKey(addressType, 4, HdPathType.NonHardened, account: alice)); Assert.NotEqual(safe.GetScriptPubKey(addressType, 4, HdPathType.NonHardened, account: alice), recoverdSafe.GetScriptPubKey(addressType, 4, HdPathType.NonHardened, account: bob)); Assert.NotEqual(safe.GetScriptPubKey(addressType, 4, account: alice), safe.GetScriptPubKey(addressType, 4)); } Assert.Equal(DateTimeOffset.UtcNow.Date, safe.CreationTime.Date); Assert.InRange(safe.CreationTime, Safe.EarliestPossibleCreationTime, DateTimeOffset.UtcNow); Assert.True(wantedCreation < recoverdSafe.CreationTime); Assert.Equal(network, safe.Network); Assert.Equal(network, loadedSafe.Network); Assert.Equal(network, recoverdSafe.Network); } finally { safe?.Delete(); recoverdSafe?.Delete(); } } }
/// <summary> /// /// </summary> /// <param name="account">if null then default safe, if doesn't contain, then exception</param> /// <returns></returns> public IEnumerable <SafeHistoryRecord> GetSafeHistory(SafeAccount account = null) { AssertAccount(account); var safeHistory = new HashSet <SafeHistoryRecord>(); var transactions = GetAllChainAndMemPoolTransactionsBySafeAccount(account); var scriptPubKeys = GetTrackedScriptPubKeysBySafeAccount(account); foreach (SmartTransaction transaction in transactions) { SafeHistoryRecord record = new SafeHistoryRecord(); record.TransactionId = transaction.GetHash(); record.BlockHeight = transaction.Height; // todo: the mempool could note when it seen the transaction the first time record.TimeStamp = !transaction.Confirmed ? DateTimeOffset.UtcNow : HeaderChain.GetBlock(transaction.Height).Header.BlockTime; record.Amount = Money.Zero; //for now // how much came to our scriptpubkeys foreach (var output in transaction.Transaction.Outputs) { if (scriptPubKeys.Contains(output.ScriptPubKey)) { record.Amount += output.Value; } } foreach (var input in transaction.Transaction.Inputs) { // do we have the input? SmartTransaction inputTransaction = transactions.FirstOrDefault(x => x.GetHash() == input.PrevOut.Hash); if (default(SmartTransaction) != inputTransaction) { // if yes then deduct from amount (bitcoin output cannot be partially spent) var prevOutput = inputTransaction.Transaction.Outputs[input.PrevOut.N]; if (scriptPubKeys.Contains(prevOutput.ScriptPubKey)) { record.Amount -= prevOutput.Value; } } // if no then whatever } safeHistory.Add(record); } return(safeHistory.ToList().OrderBy(x => x.TimeStamp)); }
private void AssertAccount(SafeAccount account) { if (account == null) { if (!TracksDefaultSafe) { throw new NotSupportedException($"{nameof(TracksDefaultSafe)} cannot be {TracksDefaultSafe}"); } } else { if (!SafeAccounts.Any(x => x.Id == account.Id)) { throw new NotSupportedException($"{nameof(SafeAccounts)} does not contain the provided {nameof(account)}"); } } }
public async Task <BaseResponse> BuildTransactionAsync(string password, SafeAccount safeAccount, BitcoinAddress address, Money amount, FeeType feeType, bool subtractFeeFromAmount, Script customChangeScriptPubKey, IEnumerable <OutPoint> allowedInputs) { if (password != _password) { throw new InvalidOperationException("Wrong password"); } var result = await _walletJob.BuildTransactionAsync(address.ScriptPubKey, amount, feeType, safeAccount, (bool)Global.Config.CanSpendUnconfirmed, subtractFeeFromAmount, customChangeScriptPubKey, allowedInputs); if (result.Success) { var inputs = new List <TransactionInputModel>(); foreach (Coin coin in result.SpentCoins) { inputs.Add(new TransactionInputModel { Amount = coin.Amount.ToString(false, true), Address = coin.ScriptPubKey.GetDestinationAddress(Network).ToString(), Hash = coin.Outpoint.Hash.ToString(), Index = (int)coin.Outpoint.N }); } return(new BuildTransactionResponse { SpendsUnconfirmed = result.SpendsUnconfirmed, Fee = result.Fee.ToString(false, true), FeePercentOfSent = result.FeePercentOfSent.ToString("0.##"), Hex = result.Transaction.ToHex(), ActiveOutputAddress = result.ActiveOutput.ScriptPubKey.GetDestinationAddress(Network).ToString(), ActiveOutputAmount = result.ActiveOutput.Value.ToString(false, true), ChangeOutputAddress = result.ChangeOutput?.ScriptPubKey?.GetDestinationAddress(Network)?.ToString() ?? "", ChangeOutputAmount = result.ChangeOutput?.Value?.ToString(false, true) ?? "0", NumberOfInputs = result.SpentCoins.Count(), Inputs = inputs.ToArray(), Transaction = result.Transaction.ToString() }); } else { return(new FailureResponse { Message = result.FailingReason }); } }
public void CreationTests() { for (int i = 0; i < 2; i++) { var network = i == 0 ? Network.Main : Network.TestNet; Mnemonic mnemonic; const string path = "Wallets/TestWallet.json"; const string password = "******"; var safe = Safe.Create(out mnemonic, password, path, network); var loadedSafe = Safe.Load(password, path); var wantedCreation = DateTimeOffset.ParseExact("1998-01-01", "yyyy-MM-dd", CultureInfo.InvariantCulture); var recoverdSafe = Safe.Recover(mnemonic, password, "Wallets/RecoveredTestWallet.json", network, wantedCreation); var alice = new SafeAccount(3); var bob = new SafeAccount(4); try { foreach (AddressType addressType in Enum.GetValues(typeof(AddressType))) { Assert.Equal(safe.GetAddress(addressType, 3, account: alice), recoverdSafe.GetAddress(addressType, 3, account: alice)); Assert.Equal(safe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice), recoverdSafe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice)); Assert.NotEqual(safe.GetAddress(addressType, 4, HdPathType.Change, account: alice), recoverdSafe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice)); Assert.NotEqual(safe.GetAddress(addressType, 3, HdPathType.NonHardened, account: alice), recoverdSafe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice)); Assert.NotEqual(safe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice), recoverdSafe.GetAddress(addressType, 4, HdPathType.NonHardened, account: bob)); Assert.NotEqual(safe.GetAddress(addressType, 4, account: alice), safe.GetAddress(addressType, 4)); } Assert.Equal(DateTimeOffset.UtcNow.Date, safe.CreationTime.Date); Assert.True(Safe.EarliestPossibleCreationTime < safe.CreationTime); Assert.True(wantedCreation < recoverdSafe.CreationTime); Assert.Equal(network, safe.Network); Assert.Equal(network, loadedSafe.Network); Assert.Equal(network, recoverdSafe.Network); } finally { safe.Delete(); recoverdSafe.Delete(); } } }
/// <returns>null if didn't fail</returns> private FailureResponse GetAccount(string account, out SafeAccount safeAccount) { safeAccount = null; if (account == null) { return new FailureResponse { Message = "No request body specified" } } ; if (!Global.WalletWrapper.IsDecrypted) { return new FailureResponse { Message = "Wallet isn't decrypted" } } ; var trimmed = account; if (string.Equals(trimmed, "alice", StringComparison.OrdinalIgnoreCase)) { safeAccount = Global.WalletWrapper.AliceAccount; return(null); } else if (string.Equals(trimmed, "bob", StringComparison.OrdinalIgnoreCase)) { safeAccount = Global.WalletWrapper.BobAccount; return(null); } else { return new FailureResponse { Message = "Wrong account" } }; }
public IEnumerable <Script> GetUnusedScriptPubKeys(SafeAccount account = null, HdPathType hdPathType = HdPathType.Receive) { AssertAccount(account); HashSet <Script> scriptPubKeys = new HashSet <Script>(); int i = 0; while (true) { Script scriptPubkey = account == null?Safe.GetAddress(i, hdPathType).ScriptPubKey : Safe.GetAddress(i, hdPathType, account).ScriptPubKey; if (Tracker.IsClean(scriptPubkey)) { scriptPubKeys.Add(scriptPubkey); if (scriptPubKeys.Count >= MaxCleanAddressCount) { return(scriptPubKeys); } } i++; } }
private void UpdateSafeTrackingByPath(HdPathType hdPathType, SafeAccount account = null) { int i = 0; var cleanCount = 0; while (true) { Script scriptPubkey = account == null?Safe.GetAddress(i, hdPathType).ScriptPubKey : Safe.GetAddress(i, hdPathType, account).ScriptPubKey; Tracker.TrackedScriptPubKeys.Add(scriptPubkey); // if clean elevate cleancount and if max reached don't look for more if (Tracker.IsClean(scriptPubkey)) { cleanCount++; if (cleanCount > MaxCleanAddressCount) { return; } } i++; } }
public HistoryResponse GetHistoryResponse(SafeAccount account) => account == AliceAccount ? _historyResponseAlice : _historyResponseBob;
public ReceiveResponse GetReceiveResponse(SafeAccount account) => account == AliceAccount ? _receiveResponseAlice : _receiveResponseBob;
public Money GetIncoming(SafeAccount account) => account == AliceAccount ? _incomingAlice : _incomingBob;
public Money GetAvailable(SafeAccount account) => account == AliceAccount ? _availableAlice : _availableBob;
/// <summary> /// /// </summary> /// <param name="scriptPubKeyToSend"></param> /// <param name="amount">If Money.Zero then spend all available amount</param> /// <param name="feeType"></param> /// <param name="account"></param> /// <param name="allowUnconfirmed">Allow to spend unconfirmed transactions, if necessary</param> /// <returns></returns> public async Task <BuildTransactionResult> BuildTransactionAsync(Script scriptPubKeyToSend, Money amount, FeeType feeType, SafeAccount account = null, bool allowUnconfirmed = false) { try { AssertAccount(account); // 1. Get the script pubkey of the change. Debug.WriteLine("Select change address..."); Script changeScriptPubKey = GetUnusedScriptPubKeys(account, HdPathType.Change).FirstOrDefault(); // 2. Find all coins I can spend from the account // 3. How much money we can spend? Debug.WriteLine("Calculating available amount..."); IDictionary <Coin, bool> unspentCoins; AvailableAmount balance = GetBalance(out unspentCoins, account); Money spendableConfirmedAmount = balance.Confirmed; Money spendableUnconfirmedAmount = allowUnconfirmed ? balance.Unconfirmed : Money.Zero; Debug.WriteLine($"Spendable confirmed amount: {spendableConfirmedAmount}"); Debug.WriteLine($"Spendable unconfirmed amount: {spendableUnconfirmedAmount}"); BuildTransactionResult successfulResult = new BuildTransactionResult { Success = true, FailingReason = "" }; // 4. Get and calculate fee Debug.WriteLine("Calculating dynamic transaction fee..."); Money feePerBytes = null; try { feePerBytes = await QueryFeePerBytesAsync(feeType).ConfigureAwait(false); } catch (Exception ex) { Debug.WriteLine(ex.Message); return(new BuildTransactionResult { Success = false, FailingReason = $"Couldn't calculate transaction fee. Reason:{Environment.NewLine}{ex}" }); } bool spendAll = amount == Money.Zero; int inNum; if (spendAll) { inNum = unspentCoins.Count; } else { const int expectedMinTxSize = 1 * 148 + 2 * 34 + 10 - 1; try { inNum = SelectCoinsToSpend(unspentCoins, amount + feePerBytes * expectedMinTxSize).Count; } catch (InsufficientBalanceException) { return(NotEnoughFundsBuildTransactionResult); } } const int outNum = 2; // 1 address to send + 1 for change var estimatedTxSize = inNum * 148 + outNum * 34 + 10 + inNum; // http://bitcoin.stackexchange.com/questions/1195/how-to-calculate-transaction-size-before-sending Debug.WriteLine($"Estimated tx size: {estimatedTxSize} bytes"); Money fee = feePerBytes * estimatedTxSize; Debug.WriteLine($"Fee: {fee.ToDecimal(MoneyUnit.BTC):0.#############################}btc"); successfulResult.Fee = fee; // 5. How much to spend? Money amountToSend = null; if (spendAll) { if (allowUnconfirmed) { amountToSend = spendableConfirmedAmount + spendableUnconfirmedAmount; } else { amountToSend = spendableConfirmedAmount; } amountToSend -= fee; } else { amountToSend = amount; } // 6. Do some checks if (amountToSend < Money.Zero) { return(NotEnoughFundsBuildTransactionResult); } if (allowUnconfirmed) { if (spendableConfirmedAmount + spendableUnconfirmedAmount < amountToSend + fee) { return(NotEnoughFundsBuildTransactionResult); } } else { if (spendableConfirmedAmount < amountToSend + fee) { return(NotEnoughFundsBuildTransactionResult); } } decimal feePc = (100 * fee.ToDecimal(MoneyUnit.BTC)) / amountToSend.ToDecimal(MoneyUnit.BTC); successfulResult.FeePercentOfSent = feePc; if (feePc > 1) { Debug.WriteLine(""); Debug.WriteLine($"The transaction fee is {feePc:0.#}% of your transaction amount."); Debug.WriteLine($"Sending:\t {amountToSend.ToDecimal(MoneyUnit.BTC):0.#############################}btc"); Debug.WriteLine($"Fee:\t\t {fee.ToDecimal(MoneyUnit.BTC):0.#############################}btc"); } var confirmedAvailableAmount = spendableConfirmedAmount - spendableUnconfirmedAmount; var totalOutAmount = amountToSend + fee; if (confirmedAvailableAmount < totalOutAmount) { var unconfirmedToSend = totalOutAmount - confirmedAvailableAmount; Debug.WriteLine(""); Debug.WriteLine($"In order to complete this transaction you have to spend {unconfirmedToSend.ToDecimal(MoneyUnit.BTC):0.#############################} unconfirmed btc."); successfulResult.SpendsUnconfirmed = true; } // 7. Select coins Debug.WriteLine("Selecting coins..."); HashSet <Coin> coinsToSpend = SelectCoinsToSpend(unspentCoins, totalOutAmount); // 8. Get signing keys var signingKeys = new HashSet <ISecret>(); foreach (var coin in coinsToSpend) { var signingKey = Safe.FindPrivateKey(coin.ScriptPubKey.GetDestinationAddress(Safe.Network), Tracker.TrackedScriptPubKeys.Count, account); signingKeys.Add(signingKey); } // 9. Build the transaction Debug.WriteLine("Signing transaction..."); var builder = new TransactionBuilder(); var tx = builder .AddCoins(coinsToSpend) .AddKeys(signingKeys.ToArray()) .Send(scriptPubKeyToSend, amountToSend) .SetChange(changeScriptPubKey) .SendFees(fee) .BuildTransaction(true); if (!builder.Verify(tx)) { return new BuildTransactionResult { Success = false, FailingReason = "Couldn't build the transaction" } } ; successfulResult.Transaction = tx; return(successfulResult); } catch (Exception ex) { return(new BuildTransactionResult { Success = false, FailingReason = ex.ToString() }); } }
public void MaxAmountTest() { Network network = Network.TestNet; SafeAccount account = new SafeAccount(1); string path = Path.Combine(Helpers.CommittedWalletsFolderPath, $"Sending{network}.json"); const string password = ""; Safe safe = Safe.Load(password, path); Assert.Equal(network, safe.Network); Debug.WriteLine($"Unique Safe ID: {safe.UniqueId}"); // create walletjob WalletJob walletJob = new WalletJob(Helpers.SocksPortHandler, Helpers.ControlPortClient, safe, trackDefaultSafe: false, accountsToTrack: account); // note some event WalletJob.ConnectedNodeCountChanged += delegate { if (WalletJob.MaxConnectedNodeCount == WalletJob.ConnectedNodeCount) { Debug.WriteLine( $"{nameof(WalletJob.MaxConnectedNodeCount)} reached: {WalletJob.MaxConnectedNodeCount}"); } else { Debug.WriteLine($"{nameof(WalletJob.ConnectedNodeCount)}: {WalletJob.ConnectedNodeCount}"); } }; walletJob.StateChanged += delegate { Debug.WriteLine($"{nameof(walletJob.State)}: {walletJob.State}"); }; // start syncing var cts = new CancellationTokenSource(); var walletJobTask = walletJob.StartAsync(cts.Token); Task reportTask = Helpers.ReportAsync(cts.Token, walletJob); try { // wait until blocks are synced while (walletJob.State <= WalletState.SyncingMemPool) { Task.Delay(1000).Wait(); } var receive = walletJob.GetUnusedScriptPubKeys(account, HdPathType.Receive).FirstOrDefault(); IDictionary <Coin, bool> unspentCoins; var bal = walletJob.GetBalance(out unspentCoins, account); var res = walletJob.BuildTransactionAsync(receive, Money.Zero, FeeType.Low, account, allowUnconfirmed: true).Result; Assert.True(res.Success); Assert.True(res.FailingReason == ""); Debug.WriteLine($"Fee: {res.Fee}"); Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %"); Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}"); Debug.WriteLine($"Transaction: {res.Transaction}"); var foundReceive = false; Assert.InRange(res.Transaction.Outputs.Count, 1, 2); foreach (var output in res.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.True(bal.Confirmed + bal.Unconfirmed - res.Fee == output.Value); } } Assert.True(foundReceive); var txProbArrived = false; var prevCount = walletJob.Tracker.TrackedTransactions.Count; walletJob.Tracker.TrackedTransactions.CollectionChanged += delegate { var actCount = walletJob.Tracker.TrackedTransactions.Count; // if arrived if (actCount > prevCount) { txProbArrived = true; } else { prevCount = actCount; } }; var sendRes = walletJob.SendTransactionAsync(res.Transaction).Result; Assert.True(sendRes.Success); Assert.True(sendRes.FailingReason == ""); while (txProbArrived == false) { Debug.WriteLine("Waiting for transaction..."); Task.Delay(1000).Wait(); } Debug.WriteLine("TrackedTransactions collection changed"); Assert.True(walletJob.Tracker.TrackedTransactions.Any(x => x.Transaction.GetHash() == res.Transaction.GetHash())); Debug.WriteLine("Transaction arrived"); } finally { cts.Cancel(); Task.WhenAll(reportTask, walletJobTask).Wait(); } }
public async Task SendsFailGracefullyTestAsync() { Network network = Network.TestNet; SafeAccount account = new SafeAccount(1); string path = Path.Combine(Helpers.CommittedWalletsFolderPath, $"Sending{network}.json"); const string password = ""; Safe safe = await Safe.LoadAsync(password, path, network); Assert.Equal(network, safe.Network); Debug.WriteLine($"Unique Safe ID: {safe.UniqueId}"); // create walletjob WalletJob walletJob = new WalletJob(); await walletJob.InitializeAsync(Helpers.SocksPortHandler, Helpers.ControlPortClient, safe, trackDefaultSafe : false, accountsToTrack : account); // note some event walletJob.ConnectedNodeCountChanged += WalletJob_ConnectedNodeCountChanged; walletJob.StateChanged += WalletJob_StateChanged; // start syncing var cts = new CancellationTokenSource(); var walletJobTask = walletJob.StartAsync(cts.Token); Task reportTask = Helpers.ReportAsync(cts.Token, walletJob); try { // wait until blocks are synced while (walletJob.State <= WalletState.SyncingMemPool) { await Task.Delay(1000); } var history = await walletJob.GetSafeHistoryAsync(account); foreach (var record in history) { Debug.WriteLine($"{record.TransactionId} {record.Amount} {record.Confirmed}"); } var receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault(); var bal = (await walletJob.GetBalanceAsync(account)).Available; // Not enough fee Money amountToSend = (bal.Confirmed + bal.Unconfirmed) - new Money(1m, MoneyUnit.Satoshi); var res = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account, allowUnconfirmed : true); Assert.False(res.Success); Assert.NotEmpty(res.FailingReason); Debug.WriteLine($"Expected FailingReason: {res.FailingReason}"); // That's not how you spend all amountToSend = (bal.Confirmed + bal.Unconfirmed); res = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account, allowUnconfirmed : true); Assert.False(res.Success); Assert.NotEmpty(res.FailingReason); Debug.WriteLine($"Expected FailingReason: {res.FailingReason}"); // Too much amountToSend = (bal.Confirmed + bal.Unconfirmed) + new Money(1, MoneyUnit.BTC); res = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account, allowUnconfirmed : true); Assert.True(res.Success == false); Assert.True(res.FailingReason != ""); Debug.WriteLine($"Expected FailingReason: {res.FailingReason}"); // Minus amountToSend = new Money(-1m, MoneyUnit.BTC); res = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account, allowUnconfirmed : true); Assert.False(res.Success); Assert.NotEmpty(res.FailingReason); Debug.WriteLine($"Expected FailingReason: {res.FailingReason}"); // Default account is disabled amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2; await Assert.ThrowsAsync <NotSupportedException>(async() => await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, allowUnconfirmed: true)).ContinueWith(t => {}); // No such account amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2; await Assert.ThrowsAsync <NotSupportedException>(async() => await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, new SafeAccount(23421), allowUnconfirmed: true)).ContinueWith(t => { }); } finally { cts?.Cancel(); await Task.WhenAll(reportTask, walletJobTask); walletJob.ConnectedNodeCountChanged -= WalletJob_ConnectedNodeCountChanged; walletJob.StateChanged -= WalletJob_StateChanged; cts?.Dispose(); reportTask?.Dispose(); walletJobTask?.Dispose(); } }
public async Task SendTestAsync() { Network network = Network.TestNet; SafeAccount account = new SafeAccount(1); string path = Path.Combine(Helpers.CommittedWalletsFolderPath, $"Sending{network}.json"); const string password = ""; Safe safe = await Safe.LoadAsync(password, path); Assert.Equal(network, safe.Network); Debug.WriteLine($"Unique Safe ID: {safe.UniqueId}"); // create walletjob WalletJob walletJob = new WalletJob(); await walletJob.InitializeAsync(Helpers.SocksPortHandler, Helpers.ControlPortClient, safe, trackDefaultSafe : false, accountsToTrack : account); // note some event walletJob.ConnectedNodeCountChanged += WalletJob_ConnectedNodeCountChanged; walletJob.StateChanged += WalletJob_StateChanged; // start syncing var cts = new CancellationTokenSource(); var walletJobTask = walletJob.StartAsync(cts.Token); Task reportTask = Helpers.ReportAsync(cts.Token, walletJob); try { // wait until blocks are synced while (walletJob.State <= WalletState.SyncingMemPool) { await Task.Delay(1000); } foreach (var r in await walletJob.GetSafeHistoryAsync(account)) { Debug.WriteLine(r.TransactionId); } # region Basic var receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault(); var getBalanceResult = await walletJob.GetBalanceAsync(account); var bal = getBalanceResult.Available; Money amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2; var res = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account, allowUnconfirmed : true); Assert.True(res.Success); Assert.Empty(res.FailingReason); Assert.Equal(receive, res.ActiveOutput.ScriptPubKey); Assert.Equal(amountToSend, res.ActiveOutput.Value); Assert.NotNull(res.ChangeOutput); Assert.Contains(res.Transaction.Outputs, x => x.Value == res.ChangeOutput.Value); Debug.WriteLine($"Fee: {res.Fee}"); Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %"); Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}"); Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}"); Debug.WriteLine($"Change Output: {res.ChangeOutput.Value.ToString(false, true)} {res.ChangeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Debug.WriteLine($"TxId: {res.Transaction.GetHash()}"); var foundReceive = false; Assert.InRange(res.Transaction.Outputs.Count, 1, 2); foreach (var output in res.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend, output.Value); } } Assert.True(foundReceive); _txProbArrived = false; _prevCount = (await walletJob.GetTrackerAsync()).TrackedTransactions.Count; _currentWalletJob = walletJob; (await walletJob.GetTrackerAsync()).TrackedTransactions.CollectionChanged += TrackedTransactions_CollectionChangedAsync; var sendRes = await walletJob.SendTransactionAsync(res.Transaction); Assert.True(sendRes.Success); Assert.Empty(sendRes.FailingReason); while (!_txProbArrived) { Debug.WriteLine("Waiting for transaction..."); await Task.Delay(1000); } Debug.WriteLine("TrackedTransactions collection changed"); Assert.Contains((await walletJob.GetTrackerAsync()).TrackedTransactions, x => x.Transaction.GetHash() == res.Transaction.GetHash()); Debug.WriteLine("Transaction arrived"); receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault(); bal = (await walletJob.GetBalanceAsync(account)).Available; amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2; #endregion #region SubtractFeeFromAmount receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault(); getBalanceResult = await walletJob.GetBalanceAsync(account); bal = getBalanceResult.Available; amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2; res = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account, allowUnconfirmed : true, subtractFeeFromAmount : true); Assert.True(res.Success); Assert.Empty(res.FailingReason); Assert.Equal(receive, res.ActiveOutput.ScriptPubKey); Assert.Equal(amountToSend - res.Fee, res.ActiveOutput.Value); Assert.NotNull(res.ChangeOutput); Assert.Contains(res.Transaction.Outputs, x => x.Value == res.ChangeOutput.Value); Debug.WriteLine($"Fee: {res.Fee}"); Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %"); Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}"); Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}"); Debug.WriteLine($"Change Output: {res.ChangeOutput.Value.ToString(false, true)} {res.ChangeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Debug.WriteLine($"TxId: {res.Transaction.GetHash()}"); foundReceive = false; Assert.InRange(res.Transaction.Outputs.Count, 1, 2); foreach (var output in res.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend - res.Fee, output.Value); } } Assert.True(foundReceive); #endregion #region CustomChange receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault(); var customChange = new Key().ScriptPubKey; getBalanceResult = await walletJob.GetBalanceAsync(account); bal = getBalanceResult.Available; amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2; res = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account, allowUnconfirmed : true, subtractFeeFromAmount : true, customChangeScriptPubKey : customChange); Assert.True(res.Success); Assert.Empty(res.FailingReason); Assert.Equal(receive, res.ActiveOutput.ScriptPubKey); Assert.Equal(amountToSend - res.Fee, res.ActiveOutput.Value); Assert.NotNull(res.ChangeOutput); Assert.Equal(customChange, res.ChangeOutput.ScriptPubKey); Assert.Contains(res.Transaction.Outputs, x => x.Value == res.ChangeOutput.Value); Debug.WriteLine($"Fee: {res.Fee}"); Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %"); Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}"); Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}"); Debug.WriteLine($"Change Output: {res.ChangeOutput.Value.ToString(false, true)} {res.ChangeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Debug.WriteLine($"TxId: {res.Transaction.GetHash()}"); foundReceive = false; Assert.InRange(res.Transaction.Outputs.Count, 1, 2); foreach (var output in res.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend - res.Fee, output.Value); } } Assert.True(foundReceive); #endregion #region LowFee res = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account, allowUnconfirmed : true); Assert.True(res.Success); Assert.Empty(res.FailingReason); Assert.Equal(receive, res.ActiveOutput.ScriptPubKey); Assert.Equal(amountToSend, res.ActiveOutput.Value); Assert.NotNull(res.ChangeOutput); Assert.Contains(res.Transaction.Outputs, x => x.Value == res.ChangeOutput.Value); Debug.WriteLine($"Fee: {res.Fee}"); Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %"); Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}"); Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}"); Debug.WriteLine($"Change Output: {res.ChangeOutput.Value.ToString(false, true)} {res.ChangeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Debug.WriteLine($"TxId: {res.Transaction.GetHash()}"); foundReceive = false; Assert.InRange(res.Transaction.Outputs.Count, 1, 2); foreach (var output in res.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend, output.Value); } } Assert.True(foundReceive); #endregion #region MediumFee res = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Medium, account, allowUnconfirmed : true); Assert.True(res.Success); Assert.Empty(res.FailingReason); Assert.Equal(receive, res.ActiveOutput.ScriptPubKey); Assert.Equal(amountToSend, res.ActiveOutput.Value); Assert.NotNull(res.ChangeOutput); Assert.Contains(res.Transaction.Outputs, x => x.Value == res.ChangeOutput.Value); Debug.WriteLine($"Fee: {res.Fee}"); Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %"); Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}"); Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}"); Debug.WriteLine($"Change Output: {res.ChangeOutput.Value.ToString(false, true)} {res.ChangeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Debug.WriteLine($"TxId: {res.Transaction.GetHash()}"); foundReceive = false; Assert.InRange(res.Transaction.Outputs.Count, 1, 2); foreach (var output in res.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend, output.Value); } } Assert.True(foundReceive); #endregion #region HighFee res = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.High, account, allowUnconfirmed : true); Assert.True(res.Success); Assert.Empty(res.FailingReason); Assert.Equal(receive, res.ActiveOutput.ScriptPubKey); Assert.Equal(amountToSend, res.ActiveOutput.Value); Assert.NotNull(res.ChangeOutput); Assert.Contains(res.Transaction.Outputs, x => x.Value == res.ChangeOutput.Value); Debug.WriteLine($"Fee: {res.Fee}"); Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %"); Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}"); Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}"); Debug.WriteLine($"Change Output: {res.ChangeOutput.Value.ToString(false, true)} {res.ChangeOutput.ScriptPubKey.GetDestinationAddress(network)}"); Debug.WriteLine($"TxId: {res.Transaction.GetHash()}"); foundReceive = false; Assert.InRange(res.Transaction.Outputs.Count, 1, 2); foreach (var output in res.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend, output.Value); } } Assert.True(foundReceive); Assert.InRange(res.Fee, Money.Zero, res.Fee); Assert.InRange(res.Fee, res.Fee, res.Fee); _txProbArrived = false; _prevCount = (await walletJob.GetTrackerAsync()).TrackedTransactions.Count; _currentWalletJob = walletJob; (await walletJob.GetTrackerAsync()).TrackedTransactions.CollectionChanged += TrackedTransactions_CollectionChangedAsync; sendRes = await walletJob.SendTransactionAsync(res.Transaction); Assert.True(sendRes.Success); Assert.Empty(sendRes.FailingReason); while (_txProbArrived == false) { Debug.WriteLine("Waiting for transaction..."); await Task.Delay(1000); } Debug.WriteLine("TrackedTransactions collection changed"); Assert.Contains((await walletJob.GetTrackerAsync()).TrackedTransactions, x => x.Transaction.GetHash() == res.Transaction.GetHash()); Debug.WriteLine("Transaction arrived"); #endregion #region MaxAmount receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault(); bal = (await walletJob.GetBalanceAsync(account)).Available; res = await walletJob.BuildTransactionAsync(receive, Money.Zero, FeeType.Low, account, allowUnconfirmed : true); Assert.True(res.Success); Assert.Empty(res.FailingReason); Assert.Equal(receive, res.ActiveOutput.ScriptPubKey); Assert.Null(res.ChangeOutput); Debug.WriteLine($"Fee: {res.Fee}"); Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %"); Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}"); Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}"); Debug.WriteLine($"TxId: {res.Transaction.GetHash()}"); Assert.Single(res.Transaction.Outputs); var maxBuiltTxOutput = res.Transaction.Outputs.Single(); Assert.Equal(receive, maxBuiltTxOutput.ScriptPubKey); Assert.Equal(bal.Confirmed + bal.Unconfirmed - res.Fee, maxBuiltTxOutput.Value); #endregion #region InputSelection receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault(); bal = (await walletJob.GetBalanceAsync(account)).Available; var inputCountBefore = res.SpentCoins.Count(); res = await walletJob.BuildTransactionAsync(receive, Money.Zero, FeeType.Low, account, allowUnconfirmed : true, allowedInputs : res.SpentCoins.Where((x, i) => i == 0 || i % 2 == 0).Select(x => x.Outpoint)); Assert.True(inputCountBefore >= res.SpentCoins.Count()); Assert.Equal(res.SpentCoins.Count(), res.Transaction.Inputs.Count); Assert.True(res.Success); Assert.Empty(res.FailingReason); Assert.Equal(receive, res.ActiveOutput.ScriptPubKey); Assert.Null(res.ChangeOutput); Debug.WriteLine($"Fee: {res.Fee}"); Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %"); Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}"); Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}"); Debug.WriteLine($"TxId: {res.Transaction.GetHash()}"); Assert.Single(res.Transaction.Outputs); res = await walletJob.BuildTransactionAsync(receive, Money.Zero, FeeType.Low, account, allowUnconfirmed : true, allowedInputs : new[] { res.SpentCoins.Select(x => x.Outpoint).First() }); Assert.Single(res.Transaction.Inputs); Assert.Single(res.Transaction.Outputs); Assert.Single(res.SpentCoins); Assert.Null(res.ChangeOutput); #endregion }
public HashSet <SmartTransaction> GetAllChainAndMemPoolTransactionsBySafeAccount(SafeAccount account = null) { HashSet <Script> trackedScriptPubkeys = GetTrackedScriptPubKeysBySafeAccount(account); var foundTransactions = new HashSet <SmartTransaction>(); foreach (var spk in trackedScriptPubkeys) { HashSet <SmartTransaction> rec; HashSet <SmartTransaction> spent; if (TryFindAllChainAndMemPoolTransactions(spk, out rec, out spent)) { foreach (var tx in rec) { foundTransactions.Add(tx); } foreach (var tx in spent) { foundTransactions.Add(tx); } } } return(foundTransactions); }
public void SendsFailGracefullyTest() { Network network = Network.TestNet; SafeAccount account = new SafeAccount(1); string path = Path.Combine(Helpers.CommittedWalletsFolderPath, $"Sending{network}.json"); const string password = ""; Safe safe = Safe.Load(password, path); Assert.Equal(network, safe.Network); Debug.WriteLine($"Unique Safe ID: {safe.UniqueId}"); // create walletjob WalletJob walletJob = new WalletJob(Helpers.SocksPortHandler, Helpers.ControlPortClient, safe, trackDefaultSafe: false, accountsToTrack: account); // note some event WalletJob.ConnectedNodeCountChanged += delegate { if (WalletJob.MaxConnectedNodeCount == WalletJob.ConnectedNodeCount) { Debug.WriteLine( $"{nameof(WalletJob.MaxConnectedNodeCount)} reached: {WalletJob.MaxConnectedNodeCount}"); } else { Debug.WriteLine($"{nameof(WalletJob.ConnectedNodeCount)}: {WalletJob.ConnectedNodeCount}"); } }; walletJob.StateChanged += delegate { Debug.WriteLine($"{nameof(walletJob.State)}: {walletJob.State}"); }; // start syncing var cts = new CancellationTokenSource(); var walletJobTask = walletJob.StartAsync(cts.Token); Task reportTask = Helpers.ReportAsync(cts.Token, walletJob); try { // wait until blocks are synced while (walletJob.State <= WalletState.SyncingMemPool) { Task.Delay(1000).Wait(); } var history = walletJob.GetSafeHistory(account); foreach (var record in history) { Debug.WriteLine($"{record.TransactionId} {record.Amount} {record.Confirmed}"); } var receive = walletJob.GetUnusedScriptPubKeys(account, HdPathType.Receive).FirstOrDefault(); IDictionary <Coin, bool> unspentCoins; var bal = walletJob.GetBalance(out unspentCoins, account); // Not enough fee Money amountToSend = (bal.Confirmed + bal.Unconfirmed) - new Money(1m, MoneyUnit.Satoshi); var res = walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account, allowUnconfirmed: true).Result; Assert.True(res.Success == false); Assert.True(res.FailingReason != ""); Debug.WriteLine($"Expected FailingReason: {res.FailingReason}"); // That's not how you spend all amountToSend = (bal.Confirmed + bal.Unconfirmed); res = walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account, allowUnconfirmed: true).Result; Assert.True(res.Success == false); Assert.True(res.FailingReason != ""); Debug.WriteLine($"Expected FailingReason: {res.FailingReason}"); // Too much amountToSend = (bal.Confirmed + bal.Unconfirmed) + new Money(1, MoneyUnit.BTC); res = walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account, allowUnconfirmed: true).Result; Assert.True(res.Success == false); Assert.True(res.FailingReason != ""); Debug.WriteLine($"Expected FailingReason: {res.FailingReason}"); // Minus amountToSend = new Money(-1m, MoneyUnit.BTC); res = walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account, allowUnconfirmed: true).Result; Assert.True(res.Success == false); Assert.True(res.FailingReason != ""); Debug.WriteLine($"Expected FailingReason: {res.FailingReason}"); // Default account is disabled amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2; Assert.ThrowsAsync <NotSupportedException>(async() => await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, allowUnconfirmed: true).ConfigureAwait(false)).ContinueWith(t => {}).Wait(); // No such account amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2; Assert.ThrowsAsync <NotSupportedException>(async() => await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, new SafeAccount(23421), allowUnconfirmed: true).ConfigureAwait(false)).ContinueWith(t => { }).Wait(); } finally { cts.Cancel(); Task.WhenAll(reportTask, walletJobTask).Wait(); } }
public async Task SendTestAsync() { Network network = Network.TestNet; SafeAccount account = new SafeAccount(1); string path = Path.Combine(Helpers.CommittedWalletsFolderPath, $"Sending{network}.json"); const string password = ""; Safe safe = await Safe.LoadAsync(password, path); Assert.Equal(network, safe.Network); Debug.WriteLine($"Unique Safe ID: {safe.UniqueId}"); // create walletjob WalletJob walletJob = new WalletJob(); await walletJob.InitializeAsync(Helpers.SocksPortHandler, Helpers.ControlPortClient, safe, trackDefaultSafe : false, accountsToTrack : account); // note some event walletJob.ConnectedNodeCountChanged += WalletJob_ConnectedNodeCountChanged; walletJob.StateChanged += WalletJob_StateChanged; // start syncing var cts = new CancellationTokenSource(); var walletJobTask = walletJob.StartAsync(cts.Token); Task reportTask = Helpers.ReportAsync(cts.Token, walletJob); try { // wait until blocks are synced while (walletJob.State <= WalletState.SyncingMemPool) { await Task.Delay(1000); } foreach (var r in await walletJob.GetSafeHistoryAsync(account)) { Debug.WriteLine(r.TransactionId); } var record = (await walletJob.GetSafeHistoryAsync(account)).FirstOrDefault(); Debug.WriteLine(record.Confirmed); Debug.WriteLine(record.Amount); var receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault(); var getBalanceResult = await walletJob.GetBalanceAsync(account); var bal = getBalanceResult.Available; Money amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2; var res = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account, allowUnconfirmed : true); Assert.True(res.Success); Assert.Empty(res.FailingReason); Debug.WriteLine($"Fee: {res.Fee}"); Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %"); Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}"); Debug.WriteLine($"Transaction: {res.Transaction}"); var foundReceive = false; Assert.InRange(res.Transaction.Outputs.Count, 1, 2); foreach (var output in res.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend, output.Value); } } Assert.True(foundReceive); _txProbArrived = false; _prevCount = (await walletJob.GetTrackerAsync()).TrackedTransactions.Count; _currentWalletJob = walletJob; (await walletJob.GetTrackerAsync()).TrackedTransactions.CollectionChanged += TrackedTransactions_CollectionChangedAsync; var sendRes = await walletJob.SendTransactionAsync(res.Transaction); Assert.True(sendRes.Success); Assert.Empty(sendRes.FailingReason); while (!_txProbArrived) { Debug.WriteLine("Waiting for transaction..."); await Task.Delay(1000); } Debug.WriteLine("TrackedTransactions collection changed"); Assert.Contains((await walletJob.GetTrackerAsync()).TrackedTransactions, x => x.Transaction.GetHash() == res.Transaction.GetHash()); Debug.WriteLine("Transaction arrived"); receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault(); bal = (await walletJob.GetBalanceAsync(account)).Available; amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2; #region LowFee var resLow = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account, allowUnconfirmed : true); Assert.True(resLow.Success); Assert.Empty(resLow.FailingReason); Debug.WriteLine($"Fee: {resLow.Fee}"); Debug.WriteLine($"FeePercentOfSent: {resLow.FeePercentOfSent} %"); Debug.WriteLine($"SpendsUnconfirmed: {resLow.SpendsUnconfirmed}"); Debug.WriteLine($"Transaction: {resLow.Transaction}"); foundReceive = false; Assert.InRange(resLow.Transaction.Outputs.Count, 1, 2); foreach (var output in resLow.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend, output.Value); } } Assert.True(foundReceive); #endregion #region MediumFee var resMedium = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Medium, account, allowUnconfirmed : true); Assert.True(resMedium.Success); Assert.Empty(resMedium.FailingReason); Debug.WriteLine($"Fee: {resMedium.Fee}"); Debug.WriteLine($"FeePercentOfSent: {resMedium.FeePercentOfSent} %"); Debug.WriteLine($"SpendsUnconfirmed: {resMedium.SpendsUnconfirmed}"); Debug.WriteLine($"Transaction: {resMedium.Transaction}"); foundReceive = false; Assert.InRange(resMedium.Transaction.Outputs.Count, 1, 2); foreach (var output in resMedium.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend, output.Value); } } Assert.True(foundReceive); #endregion #region HighFee var resHigh = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.High, account, allowUnconfirmed : true); Assert.True(resHigh.Success); Assert.Empty(resHigh.FailingReason); Debug.WriteLine($"Fee: {resHigh.Fee}"); Debug.WriteLine($"FeePercentOfSent: {resHigh.FeePercentOfSent} %"); Debug.WriteLine($"SpendsUnconfirmed: {resHigh.SpendsUnconfirmed}"); Debug.WriteLine($"Transaction: {resHigh.Transaction}"); foundReceive = false; Assert.InRange(resHigh.Transaction.Outputs.Count, 1, 2); foreach (var output in resHigh.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(amountToSend, output.Value); } } Assert.True(foundReceive); #endregion Assert.InRange(resLow.Fee, Money.Zero, resMedium.Fee); Assert.InRange(resMedium.Fee, resLow.Fee, resHigh.Fee); _txProbArrived = false; _prevCount = (await walletJob.GetTrackerAsync()).TrackedTransactions.Count; _currentWalletJob = walletJob; (await walletJob.GetTrackerAsync()).TrackedTransactions.CollectionChanged += TrackedTransactions_CollectionChangedAsync; sendRes = await walletJob.SendTransactionAsync(resHigh.Transaction); Assert.True(sendRes.Success); Assert.Empty(sendRes.FailingReason); while (_txProbArrived == false) { Debug.WriteLine("Waiting for transaction..."); await Task.Delay(1000); } Debug.WriteLine("TrackedTransactions collection changed"); Assert.Contains((await walletJob.GetTrackerAsync()).TrackedTransactions, x => x.Transaction.GetHash() == resHigh.Transaction.GetHash()); Debug.WriteLine("Transaction arrived"); receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault(); bal = (await walletJob.GetBalanceAsync(account)).Available; res = await walletJob.BuildTransactionAsync(receive, Money.Zero, FeeType.Low, account, allowUnconfirmed : true); Assert.True(res.Success); Assert.Empty(res.FailingReason); Debug.WriteLine($"Fee: {res.Fee}"); Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %"); Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}"); Debug.WriteLine($"Transaction: {res.Transaction}"); foundReceive = false; Assert.InRange(res.Transaction.Outputs.Count, 1, 2); foreach (var output in res.Transaction.Outputs) { if (output.ScriptPubKey == receive) { foundReceive = true; Assert.Equal(bal.Confirmed + bal.Unconfirmed - res.Fee, output.Value); } } Assert.True(foundReceive); _txProbArrived = false; _prevCount = (await walletJob.GetTrackerAsync()).TrackedTransactions.Count; _currentWalletJob = walletJob; (await walletJob.GetTrackerAsync()).TrackedTransactions.CollectionChanged += TrackedTransactions_CollectionChangedAsync; sendRes = await walletJob.SendTransactionAsync(res.Transaction); Assert.True(sendRes.Success); Assert.Empty(sendRes.FailingReason); while (_txProbArrived == false) { Debug.WriteLine("Waiting for transaction..."); await Task.Delay(1000); } Debug.WriteLine("TrackedTransactions collection changed"); Assert.Contains((await walletJob.GetTrackerAsync()).TrackedTransactions, x => x.Transaction.GetHash() == res.Transaction.GetHash()); Debug.WriteLine("Transaction arrived"); } finally { (await walletJob.GetTrackerAsync()).TrackedTransactions.CollectionChanged -= TrackedTransactions_CollectionChangedAsync; cts.Cancel(); await Task.WhenAll(reportTask, walletJobTask); walletJob.ConnectedNodeCountChanged -= WalletJob_ConnectedNodeCountChanged; walletJob.StateChanged -= WalletJob_StateChanged; cts?.Dispose(); reportTask?.Dispose(); walletJobTask?.Dispose(); } }