Exemple #1
0
        // TODO: These can be more efficient, i.e. remove the wallet calls from GetHistory
        // And use a different model for Withdrawals. It doesn't quite map to the Withdrawal class.

        /// <summary>
        /// Get the history of successful withdrawals.
        /// </summary>
        /// <param name="maximumEntriesToReturn">The maximum number of entries to return.</param>
        /// <returns>A <see cref="WithdrawalModel"/> object containing a history of withdrawals.</returns>
        public List <WithdrawalModel> GetHistory(int maximumEntriesToReturn)
        {
            var result = new List <WithdrawalModel>();

            ICrossChainTransfer[] transfers = this.crossChainTransferStore.GetTransfersByStatus(new[] { CrossChainTransferStatus.SeenInBlock });

            // TODO: Need to check if this is only used for wallet UI purposes and has no consensus implications
            foreach (ICrossChainTransfer transfer in transfers.OrderByDescending(t => t.BlockHeight)) // t.PartialTransaction.Time
            {
                if (maximumEntriesToReturn-- <= 0)
                {
                    break;
                }

                // Extract the withdrawal details from the recorded "PartialTransaction".
                IWithdrawal withdrawal = this.withdrawalExtractor.ExtractWithdrawalFromTransaction(transfer.PartialTransaction, transfer.BlockHash, (int)transfer.BlockHeight);

                var model = new WithdrawalModel();
                model.withdrawal     = withdrawal;
                model.TransferStatus = transfer?.Status.ToString();

                result.Add(model);
            }

            return(result);
        }
        /// <inheritdoc />
        public List <(Transaction, TransactionData, IWithdrawal)> FindWithdrawalTransactions(uint256 depositId = null)
        {
            lock (this.lockObject)
            {
                List <(Transaction, TransactionData, IWithdrawal)> withdrawals = new List <(Transaction, TransactionData, IWithdrawal)>();

                foreach (TransactionData transactionData in this.Wallet.MultiSigAddress.Transactions)
                {
                    Transaction walletTran = transactionData.GetFullTransaction(this.network);
                    IWithdrawal withdrawal = this.withdrawalExtractor.ExtractWithdrawalFromTransaction(walletTran, transactionData.BlockHash, transactionData.BlockHeight ?? 0);
                    if (withdrawal == null)
                    {
                        continue;
                    }

                    if (depositId != null && withdrawal.DepositId != depositId)
                    {
                        continue;
                    }

                    withdrawals.Add((walletTran, transactionData, withdrawal));
                }

                return(withdrawals);
            }
        }
Exemple #3
0
        public AccountWithdrawalResponse Withdraw(string accountNumber, decimal amount)
        {
            AccountWithdrawalResponse response = new AccountWithdrawalResponse
            {
                Success = false,
                Account = _accountRepository.LoadAccount(accountNumber)
            };

            if (response.Account == null)
            {
                response.Message = $"{accountNumber} is not a valid account.";
            }
            else
            {
                //var someThing =  DIContainer.Kernel.GetBindings(DIContainer.Kernel.GetService().Withdraw()
                IWithdrawal depositRule = WithdrawalRulesFactory.Create(response.Account.Type);
                response = depositRule.Withdraw(response.Account, amount);
            }

            if (response.Success)
            {
                _accountRepository.SaveAccount(response.Account);
            }

            return(response);
        }
        // TODO: These can be more efficient, i.e. remove the wallet calls from GetHistory
        // And use a different model for Withdrawals. It doesn't quite map to the Withdrawal class.

        /// <summary>
        /// Get the history of successful withdrawals.
        /// </summary>
        /// <param name="maximumEntriesToReturn">The maximum number of entries to return.</param>
        /// <returns>A <see cref="WithdrawalModel"/> object containing a history of withdrawals.</returns>
        public List <WithdrawalModel> GetHistory(int maximumEntriesToReturn)
        {
            var result = new List <WithdrawalModel>();

            ICrossChainTransfer[] transfers = this.crossChainTransferStore.GetTransfersByStatus(new[] { CrossChainTransferStatus.SeenInBlock });

            foreach (ICrossChainTransfer transfer in transfers.OrderByDescending(t => t.BlockHeight))
            {
                if (maximumEntriesToReturn-- <= 0)
                {
                    break;
                }

                // Extract the withdrawal details from the recorded "PartialTransaction".
                IWithdrawal withdrawal = this.withdrawalExtractor.ExtractWithdrawalFromTransaction(transfer.PartialTransaction, transfer.BlockHash, (int)transfer.BlockHeight);

                var model = new WithdrawalModel();
                model.withdrawal     = withdrawal;
                model.TransferStatus = transfer?.Status.ToString();

                result.Add(model);
            }

            return(result);
        }
 public WithdrawalModel(Network network, IWithdrawal withdrawal, ICrossChainTransfer transfer)
 {
     this.Id             = withdrawal.Id;
     this.DepositId      = withdrawal.DepositId;
     this.Amount         = withdrawal.Amount;
     this.BlockHash      = withdrawal.BlockHash;
     this.BlockHeight    = withdrawal.BlockNumber;
     this.PayingTo       = withdrawal.TargetAddress == network.CirrusRewardDummyAddress ? "Reward Distribution" : withdrawal.TargetAddress;
     this.TransferStatus = transfer?.Status.ToString();
 }
Exemple #6
0
        public void ShouldSerialiseAsJson()
        {
            IWithdrawal withdrawal = TestingValues.GetWithdrawal();

            string asJson      = withdrawal.ToString();
            var    reconverted = JsonConvert.DeserializeObject <Withdrawal>(asJson);

            reconverted.BlockHash.Should().Be(withdrawal.BlockHash);
            reconverted.Amount.Satoshi.Should().Be(withdrawal.Amount.Satoshi);
            reconverted.BlockNumber.Should().Be(withdrawal.BlockNumber);
            reconverted.Id.Should().Be(withdrawal.Id);
            reconverted.DepositId.Should().Be(withdrawal.DepositId);
            reconverted.TargetAddress.Should().Be(withdrawal.TargetAddress);
        }
        /// <inheritdoc />
        public IEnumerable <IWithdrawal> GetWithdrawals()
        {
            foreach (TransactionData transactionData in this.Wallet.MultiSigAddress.Transactions.OrderByDescending(t => t.CreationTime))
            {
                Transaction walletTrx  = transactionData.GetFullTransaction(this.network);
                IWithdrawal withdrawal = this.withdrawalExtractor.ExtractWithdrawalFromTransaction(walletTrx, transactionData.BlockHash, transactionData.BlockHeight ?? 0);
                if (withdrawal == null)
                {
                    continue;
                }

                yield return(withdrawal);
            }
        }
        [TestCase("33333", "BasicAccount", 100, AccountType.Basic, 150, -60, true)]        //Pass, Overdraft
        public void BasicAccount_WithdrawalRuleTest(string accountNumber, string name, decimal balance, AccountType accountType, decimal amount, decimal newBalance, bool expectedResult)
        {
            IWithdrawal withdrawalRule = WithdrawalRulesFactory.Create(AccountType.Basic);

            Account account = new Account()
            {
                AccountNumber = accountNumber,
                Balance       = balance,
                Name          = name,
                Type          = accountType
            };

            AccountWithdrawalResponse response = withdrawalRule.Withdraw(account, amount);

            Assert.AreEqual(expectedResult, response.Success);
        }
 private void VerifyWithdrawalData(
     IWithdrawal withdrawals,
     long amount,
     Block block,
     int blockHeight,
     Transaction validWithdrawalTransaction,
     uint256 opReturnDepositId,
     Script targetScript)
 {
     withdrawals.Amount.Satoshi.Should().Be(amount);
     withdrawals.BlockHash.Should().Be(block.Header.GetHash());
     withdrawals.BlockNumber.Should().Be(blockHeight);
     withdrawals.Id.Should().Be(validWithdrawalTransaction.GetHash());
     withdrawals.DepositId.Should().Be(opReturnDepositId);
     withdrawals.TargetAddress.Should().Be(targetScript.GetScriptAddress(this.network).ToString());
 }
        public void StoringDepositsWhenTargetIsMultisigIsIgnoredIffOurMultisig()
        {
            var dataFolder = new DataFolder(TestBase.CreateTestDir(this));

            this.Init(dataFolder);
            this.AddFunding();
            this.AppendBlocks(WithdrawalTransactionBuilder.MinConfirmations);

            MultiSigAddress multiSigAddress = this.wallet.MultiSigAddress;

            using (ICrossChainTransferStore crossChainTransferStore = this.CreateStore())
            {
                crossChainTransferStore.Initialize();
                crossChainTransferStore.Start();

                TestBase.WaitLoopMessage(() => (this.ChainIndexer.Tip.Height == crossChainTransferStore.TipHashAndHeight.Height, $"ChainIndexer.Height:{this.ChainIndexer.Tip.Height} Store.TipHashHeight:{crossChainTransferStore.TipHashAndHeight.Height}"));
                Assert.Equal(this.ChainIndexer.Tip.HashBlock, crossChainTransferStore.TipHashAndHeight.HashBlock);

                // Forwarding money already in the multisig address to the multisig address is ignored.
                BitcoinAddress address1 = multiSigAddress.RedeemScript.Hash.GetAddress(this.network);
                BitcoinAddress address2 = new Script("").Hash.GetAddress(this.network);

                var deposit1 = new Deposit(0, new Money(160m, MoneyUnit.BTC), address1.ToString(), crossChainTransferStore.NextMatureDepositHeight, 1);
                var deposit2 = new Deposit(1, new Money(160m, MoneyUnit.BTC), address2.ToString(), crossChainTransferStore.NextMatureDepositHeight, 1);

                MaturedBlockDepositsModel[] blockDeposits = new[] { new MaturedBlockDepositsModel(
                                                                        new MaturedBlockInfoModel()
                    {
                        BlockHash   = 1,
                        BlockHeight = crossChainTransferStore.NextMatureDepositHeight
                    },
                                                                        new[] { deposit1, deposit2 }) };

                crossChainTransferStore.RecordLatestMatureDepositsAsync(blockDeposits).GetAwaiter().GetResult();

                Transaction[] partialTransactions   = crossChainTransferStore.GetTransfersByStatus(new[] { CrossChainTransferStatus.Partial }).Select(x => x.PartialTransaction).ToArray();
                Transaction[] suspendedTransactions = crossChainTransferStore.GetTransfersByStatus(new [] { CrossChainTransferStatus.Suspended }).Select(x => x.PartialTransaction).ToArray();

                // Only the deposit going towards a different multisig address is accepted. The other is ignored.
                Assert.Single(partialTransactions);
                Assert.Empty(suspendedTransactions);

                IWithdrawal withdrawal = this.withdrawalExtractor.ExtractWithdrawalFromTransaction(partialTransactions[0], null, 1);
                Assert.Equal((uint256)1, withdrawal.DepositId);
            }
        }
        /// <inheritdoc />
        public IReadOnlyList <IWithdrawal> ExtractWithdrawalsFromBlock(Block block, int blockHeight)
        {
            var withdrawals = new List <IWithdrawal>();

            foreach (Transaction transaction in block.Transactions)
            {
                IWithdrawal withdrawal = this.ExtractWithdrawalFromTransaction(transaction, block.GetHash(), blockHeight);
                if (withdrawal != null)
                {
                    withdrawals.Add(withdrawal);
                }
            }

            ReadOnlyCollection <IWithdrawal> withdrawalsFromBlock = withdrawals.AsReadOnly();

            return(withdrawalsFromBlock);
        }
Exemple #12
0
        /// <summary>
        /// Get the history of successful withdrawals.
        /// </summary>
        /// <param name="crossChainTransfers">The list of transfers to report on.</param>
        /// <param name="maximumEntriesToReturn">The maximum number of entries to return.</param>
        /// <returns>A <see cref="WithdrawalModel"/> object containing a history of withdrawals.</returns>
        public List <WithdrawalModel> GetHistory(IEnumerable <ICrossChainTransfer> crossChainTransfers, int maximumEntriesToReturn)
        {
            var result = new List <WithdrawalModel>();

            foreach (ICrossChainTransfer transfer in crossChainTransfers.OrderByDescending(t => t.BlockHeight))
            {
                if (maximumEntriesToReturn-- <= 0)
                {
                    break;
                }

                // Extract the withdrawal details from the recorded "PartialTransaction".
                IWithdrawal withdrawal = this.withdrawalExtractor.ExtractWithdrawalFromTransaction(transfer.PartialTransaction, transfer.BlockHash, (int)transfer.BlockHeight);
                var         model      = new WithdrawalModel(this.network, withdrawal, transfer);
                result.Add(model);
            }

            return(result);
        }
Exemple #13
0
        /// <inheritdoc />
        public IReadOnlyList <IWithdrawal> ExtractWithdrawalsFromBlock(Block block, int blockHeight)
        {
            var withdrawals = new List <IWithdrawal>();

            if (block.Transactions.Count <= 1)
            {
                return(withdrawals);
            }

            foreach (Transaction transaction in block.Transactions)
            {
                IWithdrawal withdrawal = this.ExtractWithdrawalFromTransaction(transaction, block.GetHash(), blockHeight);
                if (withdrawal != null)
                {
                    withdrawals.Add(withdrawal);
                }
            }

            return(withdrawals);
        }
        /// <inheritdoc />
        public List <(Transaction, IWithdrawal)> FindWithdrawalTransactions(uint256 depositId = null)
        {
            // A withdrawal is a transaction that spends funds from the multisig wallet.
            lock (this.lockObject)
            {
                var withdrawals = new List <(Transaction transaction, IWithdrawal withdrawal)>();

                foreach (TransactionData transactionData in this.Wallet.MultiSigAddress.Transactions)
                {
                    if (transactionData.SpendingDetails == null)
                    {
                        continue;
                    }

                    Transaction walletTran = this.network.CreateTransaction(transactionData.SpendingDetails.Hex);

                    if (withdrawals.Any(w => w.transaction.GetHash() == walletTran.GetHash()))
                    {
                        continue;
                    }

                    int?    blockHeight = transactionData.SpendingDetails.BlockHeight;
                    uint256 blockHash   = transactionData.SpendingDetails.BlockHash;

                    IWithdrawal withdrawal = this.withdrawalExtractor.ExtractWithdrawalFromTransaction(walletTran, blockHash, blockHeight ?? 0);
                    if (withdrawal == null)
                    {
                        continue;
                    }

                    if (depositId != null && withdrawal.DepositId != depositId)
                    {
                        continue;
                    }

                    withdrawals.Add((walletTran, withdrawal));
                }

                return(withdrawals);
            }
        }
Exemple #15
0
        private static void SetupWithdrawalScheme(WithdrawalType type)
        {
            //TODO : this is poor man's injection. For now, since its a basic application we can live without have a container and DI logic
            //to inject the scheme. Open to extension though.

            switch (type)
            {
            case WithdrawalType.PreferredDenominationRules:
                DenominationPreferenceRules rules = new DenominationPreferenceRules(new List <DenominationType> {
                    DenominationType.TwentyPound
                });
                withdrawalScheme = new WithdrawalByPreferedDenomination(moneyStore, rules);
                break;

            case WithdrawalType.LeastNumberOfItems:
                withdrawalScheme = new WithdrawalByLeastNumberOfItems(moneyStore);
                break;

            default: throw new Exception("This schema is not supported yet");
            }
        }
        private string CollectStats()
        {
            StringBuilder benchLog = new StringBuilder();

            benchLog.AppendLine();
            benchLog.AppendLine("====== Federation Wallet ======");

            (Money ConfirmedAmount, Money UnConfirmedAmount)balances = this.federationWalletManager.GetWallet().GetSpendableAmount();
            bool isFederationActive = this.federationWalletManager.IsFederationActive();

            benchLog.AppendLine("Federation Wallet: ".PadRight(LoggingConfiguration.ColumnLength)
                                + " Confirmed balance: " + balances.ConfirmedAmount.ToString().PadRight(LoggingConfiguration.ColumnLength)
                                + " Unconfirmed balance: " + balances.UnConfirmedAmount.ToString().PadRight(LoggingConfiguration.ColumnLength)
                                + " Federation Status: " + (isFederationActive ? "Active" : "Inactive"));
            benchLog.AppendLine();

            var apiSettings = (ApiSettings)this.fullNode.Services.ServiceProvider.GetService(typeof(ApiSettings));

            if (!isFederationActive)
            {
                var warning =
                    "=============================================".PadLeft(10, '=')
                    + Environment.NewLine
                    + Environment.NewLine + "Federation node not enabled. You will not be able to sign transactions until you enable it."
                    + Environment.NewLine + $"If not done previously, please import your private key using "
                    + Environment.NewLine + $"{apiSettings.ApiUri}/api/FederationWallet/{FederationWalletRouteEndPoint.ImportKey}"
                    + Environment.NewLine + $"Then enable the wallet using "
                    + Environment.NewLine + $"{apiSettings.ApiUri}/api/FederationWallet/{FederationWalletRouteEndPoint.EnableFederation}"
                    + Environment.NewLine
                    + Environment.NewLine + $"============================================".PadLeft(10, '=')
                    + Environment.NewLine;
                benchLog.AppendLine(warning);
            }

            // Display recent withdrawals (if any).
            IWithdrawal[] withdrawals = this.federationWalletManager.GetWithdrawals().Take(5).ToArray();
            if (withdrawals.Length > 0)
            {
                benchLog.AppendLine("-- Recent Withdrawals --");
                ICrossChainTransfer[] transfers = this.crossChainTransferStore.GetAsync(withdrawals.Select(w => w.DepositId).ToArray()).GetAwaiter().GetResult().ToArray();
                for (int i = 0; i < withdrawals.Length; i++)
                {
                    ICrossChainTransfer transfer   = transfers[i];
                    IWithdrawal         withdrawal = withdrawals[i];
                    string line = withdrawal.GetInfo() + " Status=" + transfer?.Status;
                    switch (transfer?.Status)
                    {
                    case CrossChainTransferStatus.FullySigned:
                        if (this.mempoolManager.InfoAsync(withdrawal.Id).GetAwaiter().GetResult() != null)
                        {
                            line += "+InMempool";
                        }
                        break;

                    case CrossChainTransferStatus.Partial:
                        line += " (" + transfer.GetSignatureCount(this.network) + "/" + this.federationGatewaySettings.MultiSigM + ")";
                        break;
                    }

                    benchLog.AppendLine(line);
                }
                benchLog.AppendLine();
            }

            benchLog.AppendLine("====== NodeStore ======");
            this.AddBenchmarkLine(benchLog, new (string, int)[] {
Exemple #17
0
 public Cashier(IWithdrawal withdrawal)
 {
     _withdrawal = withdrawal;
 }
        /// <inheritdoc />
        public bool ProcessTransaction(Transaction transaction, int?blockHeight = null, uint256 blockHash = null, Block block = null)
        {
            Guard.NotNull(transaction, nameof(transaction));
            Guard.Assert(blockHash == (blockHash ?? block?.GetHash()));

            if (this.Wallet == null)
            {
                this.logger.LogTrace("(-)");
                return(false);
            }

            bool foundReceivingTrx = false, foundSendingTrx = false;

            lock (this.lockObject)
            {
                // Check if we're trying to spend a utxo twice
                foreach (TxIn input in transaction.Inputs)
                {
                    if (!this.outpointLookup.TryGetValue(input.PrevOut, out TransactionData tTx))
                    {
                        continue;
                    }

                    // If we're trying to spend an input that is already spent, and it's not coming in a new block, don't reserve the transaction.
                    // This would be the case when blocks are synced in between CrossChainTransferStore calling
                    // FederationWalletTransactionHandler.BuildTransaction and FederationWalletManager.ProcessTransaction.
                    if (blockHeight == null && tTx.SpendingDetails?.BlockHeight != null)
                    {
                        return(false);
                    }
                }

                // Extract the withdrawal from the transaction (if any).
                IWithdrawal withdrawal = this.withdrawalExtractor.ExtractWithdrawalFromTransaction(transaction, blockHash, blockHeight ?? 0);
                if (withdrawal != null)
                {
                    // Exit if already present and included in a block.
                    List <(Transaction transaction, IWithdrawal withdrawal)> walletData = this.FindWithdrawalTransactions(withdrawal.DepositId);
                    if ((walletData.Count == 1) && (walletData[0].withdrawal.BlockNumber != 0))
                    {
                        this.logger.LogTrace("Deposit {0} Already included in block.", withdrawal.DepositId);
                        return(false);
                    }

                    // Remove this to prevent duplicates if the transaction hash has changed.
                    if (walletData.Count != 0)
                    {
                        this.logger.LogTrace("Removing duplicates for {0}", withdrawal.DepositId);
                        this.RemoveTransientTransactions(withdrawal.DepositId);
                    }
                }

                // Check the outputs.
                foreach (TxOut utxo in transaction.Outputs)
                {
                    // Check if the outputs contain one of our addresses.
                    if (this.Wallet.MultiSigAddress.ScriptPubKey == utxo.ScriptPubKey)
                    {
                        this.AddTransactionToWallet(transaction, utxo, blockHeight, blockHash, block);
                        foundReceivingTrx = true;
                    }
                }

                // Check the inputs - include those that have a reference to a transaction containing one of our scripts and the same index.
                foreach (TxIn input in transaction.Inputs)
                {
                    if (!this.outpointLookup.TryGetValue(input.PrevOut, out TransactionData tTx))
                    {
                        continue;
                    }

                    // Get the details of the outputs paid out.
                    IEnumerable <TxOut> paidOutTo = transaction.Outputs.Where(o =>
                    {
                        // If script is empty ignore it.
                        if (o.IsEmpty)
                        {
                            return(false);
                        }

                        // Check if the destination script is one of the wallet's.
                        // TODO fix this
                        bool found = this.Wallet.MultiSigAddress.ScriptPubKey == o.ScriptPubKey;

                        // Include the keys not included in our wallets (external payees).
                        if (!found)
                        {
                            return(true);
                        }

                        // Include the keys that are in the wallet but that are for receiving
                        // addresses (which would mean the user paid itself).
                        // We also exclude the keys involved in a staking transaction.
                        //return !addr.IsChangeAddress() && !transaction.IsCoinStake;
                        return(true);
                    });

                    this.AddSpendingTransactionToWallet(transaction, paidOutTo, tTx.Id, tTx.Index, blockHeight, blockHash, block);
                    foundSendingTrx = true;
                }
            }

            // Figure out what to do when this transaction is found to affect the wallet.
            if (foundSendingTrx || foundReceivingTrx)
            {
                // Save the wallet when the transaction was not included in a block.
                if (blockHeight == null)
                {
                    this.SaveWallet();
                }
            }

            return(foundSendingTrx || foundReceivingTrx);
        }
        /// <inheritdoc />
        public bool ProcessTransaction(Transaction transaction, int?blockHeight = null, Block block = null, bool isPropagated = true)
        {
            Guard.NotNull(transaction, nameof(transaction));
            uint256 hash = transaction.GetHash();

            this.logger.LogTrace("({0}:'{1}',{2}:{3})", nameof(transaction), hash, nameof(blockHeight), blockHeight);

            if (this.Wallet == null)
            {
                this.logger.LogTrace("(-)");
                return(false);
            }

            bool foundReceivingTrx = false, foundSendingTrx = false;

            lock (this.lockObject)
            {
                // Extract the withdrawal from the transaction (if any).
                IWithdrawal withdrawal = this.withdrawalExtractor.ExtractWithdrawalFromTransaction(transaction, block?.GetHash(), blockHeight ?? 0);
                if (withdrawal != null)
                {
                    // Exit if already present and included in a block.
                    List <(Transaction, TransactionData, IWithdrawal)> walletData = FindWithdrawalTransactions(withdrawal.DepositId);
                    if ((walletData.Count == 1) && (walletData[0].Item2.BlockHeight != null))
                    {
                        return(false);
                    }

                    // Remove this to prevent duplicates if the transaction hash has changed.
                    if (walletData.Count != 0)
                    {
                        this.RemoveTransientTransactions(withdrawal.DepositId);
                    }
                }

                // Check the outputs.
                foreach (TxOut utxo in transaction.Outputs)
                {
                    // Check if the outputs contain one of our addresses.
                    if (this.Wallet.MultiSigAddress.ScriptPubKey == utxo.ScriptPubKey)
                    {
                        this.AddTransactionToWallet(transaction, utxo, blockHeight, block, isPropagated);
                        foundReceivingTrx = true;
                    }
                }

                // Check the inputs - include those that have a reference to a transaction containing one of our scripts and the same index.
                foreach (TxIn input in transaction.Inputs)
                {
                    if (!this.outpointLookup.TryGetValue(input.PrevOut, out TransactionData tTx))
                    {
                        continue;
                    }

                    // Get the details of the outputs paid out.
                    IEnumerable <TxOut> paidOutTo = transaction.Outputs.Where(o =>
                    {
                        // If script is empty ignore it.
                        if (o.IsEmpty)
                        {
                            return(false);
                        }

                        // Check if the destination script is one of the wallet's.
                        // TODO fix this
                        bool found = this.Wallet.MultiSigAddress.ScriptPubKey == o.ScriptPubKey;

                        // Include the keys not included in our wallets (external payees).
                        if (!found)
                        {
                            return(true);
                        }

                        // Include the keys that are in the wallet but that are for receiving
                        // addresses (which would mean the user paid itself).
                        // We also exclude the keys involved in a staking transaction.
                        //return !addr.IsChangeAddress() && !transaction.IsCoinStake;
                        return(true);
                    });

                    this.AddSpendingTransactionToWallet(transaction, paidOutTo, tTx.Id, tTx.Index, blockHeight, block);
                    foundSendingTrx = true;
                }
            }

            // Figure out what to do when this transaction is found to affect the wallet.
            if (foundSendingTrx || foundReceivingTrx)
            {
                // Save the wallet when the transaction was not included in a block.
                if (blockHeight == null)
                {
                    this.SaveWallet();
                }
            }

            this.logger.LogTrace("(-)");
            return(foundSendingTrx || foundReceivingTrx);
        }
        /// <inheritdoc />
        public IReadOnlyList <IWithdrawal> ExtractWithdrawalsFromBlock(Block block, int blockHeight)
        {
            var withdrawals = new List <IWithdrawal>();

            // Check if this is the target height for a conversion transaction from wSTRAX back to STRAX.
            // These get returned before any other withdrawal transactions in the block to ensure consistent ordering.
            List <ConversionRequest> burnRequests = this.conversionRequestRepository.GetAllBurn(true);

            if (burnRequests != null)
            {
                foreach (ConversionRequest burnRequest in burnRequests)
                {
                    // So that we don't get stuck if we miss one inadvertently, don't break out of the loop if the height is less.
                    if (burnRequest.BlockHeight < blockHeight)
                    {
                        continue;
                    }

                    // We expect them to be ordered, so as soon as they exceed the current height, ignore the rest.
                    if (burnRequest.BlockHeight > blockHeight)
                    {
                        break;
                    }

                    // We use the transaction ID from the Ethereum chain as the request ID for the withdrawal.
                    // To parse it into a uint256 we need to trim the leading hex marker from the string.
                    uint256 requestId;
                    try
                    {
                        requestId = new uint256(burnRequest.RequestId.Replace("0x", ""));
                    }
                    catch (Exception)
                    {
                        continue;
                    }

                    withdrawals.Add(new Withdrawal(requestId, null, Money.Satoshis(burnRequest.Amount), burnRequest.DestinationAddress, burnRequest.BlockHeight, block.GetHash()));

                    // Immediately flag it as processed & persist so that it can't be added again.
                    burnRequest.Processed     = true;
                    burnRequest.RequestStatus = ConversionRequestStatus.Processed;

                    this.conversionRequestRepository.Save(burnRequest);
                }
            }

            if (block.Transactions.Count <= 1)
            {
                return(withdrawals);
            }

            foreach (Transaction transaction in block.Transactions)
            {
                IWithdrawal withdrawal = this.ExtractWithdrawalFromTransaction(transaction, block.GetHash(), blockHeight);
                if (withdrawal != null)
                {
                    withdrawals.Add(withdrawal);
                }
            }

            return(withdrawals);
        }