public IActionResult ConfirmTransaction(DestinationChain destinationChain, int transactionId, int gasPrice)
        {
            try
            {
                if (!this.ethCompatibleClientProvider.IsChainSupportedAndEnabled(destinationChain))
                {
                    return(this.Json($"{destinationChain} not enabled or supported!"));
                }

                IETHClient client = this.ethCompatibleClientProvider.GetClientForChain(destinationChain);

                return(this.Json(client.ConfirmTransactionAsync(transactionId).GetAwaiter().GetResult()));
            }
            catch (Exception e)
            {
                this.logger.Error("Exception occurred: {0}", e.ToString());

                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
        public async Task <IActionResult> ConfirmTransactionAsync(DestinationChain destinationChain, int transactionId, int gasPrice)
        {
            try
            {
                if (!this.ethCompatibleClientProvider.IsChainSupportedAndEnabled(destinationChain))
                {
                    return(this.Json($"{destinationChain} not enabled or supported!"));
                }

                IETHClient client = this.ethCompatibleClientProvider.GetClientForChain(destinationChain);

                // TODO: Maybe for convenience the gas price could come from the external API poller
                return(this.Json(await client.ConfirmTransactionAsync(transactionId, gasPrice).ConfigureAwait(false)));
            }
            catch (Exception e)
            {
                this.logger.LogError("Exception occurred: {0}", e.ToString());

                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
        /// <summary>
        /// Iterates through all unprocessed mint requests in the repository.
        /// If this node is regarded as the designated originator of the multisig transaction, it will submit the transfer transaction data to
        /// the multisig wallet contract on the Ethereum chain. This data consists of a method call to the transfer() method on the wrapped STRAX contract,
        /// as well as the intended recipient address and amount of tokens to be transferred.
        /// </summary>
        private async Task ProcessConversionRequestsAsync()
        {
            List <ConversionRequest> mintRequests = this.conversionRequestRepository.GetAllMint(true);

            if (mintRequests == null)
            {
                this.logger.LogDebug("No requests.");

                return;
            }

            this.logger.LogInformation("There are {0} unprocessed conversion mint requests.", mintRequests.Count);

            foreach (ConversionRequest request in mintRequests)
            {
                this.logger.LogInformation("Processing conversion mint request {0} on {1} chain.", request.RequestId, request.DestinationChain);

                IETHClient clientForDestChain = this.clientProvider.GetClientForChain(request.DestinationChain);

                // We are not able to simply use the entire federation member list, as only multisig nodes can be transaction originators.
                List <IFederationMember> federation = this.federationHistory.GetFederationForBlock(this.chainIndexer.GetHeader(request.BlockHeight));

                var multisig = new List <CollateralFederationMember>();

                foreach (IFederationMember member in federation)
                {
                    if (!(member is CollateralFederationMember collateralMember))
                    {
                        continue;
                    }

                    if (!collateralMember.IsMultisigMember)
                    {
                        continue;
                    }

                    if (this.network.NetworkType == NetworkType.Mainnet && !this.multisigPubKeys.Contains(collateralMember.PubKey))
                    {
                        continue;
                    }

                    multisig.Add(collateralMember);
                }

                // This should be impossible.
                if (multisig.Count == 0)
                {
                    this.logger.LogError("Sanity check failed, there are no multisig members!");

                    return;
                }

                IFederationMember designatedMember = multisig[request.BlockHeight % multisig.Count];

                bool originator = designatedMember.Equals(this.federationManager.GetCurrentFederationMember());

                // Regardless of whether we are the originator, this is a good time to check the multisig's remaining reserve
                // token balance. It is necessary to maintain a reserve as mint transactions are many times more expensive than
                // transfers. As we don't know precisely what value transactions are expected, the sole determining factor is
                // whether the reserve has a large enough balance to service the current conversion request. If not, trigger a
                // mint for a predetermined amount.
                // BigInteger reserveBalanace = await this.ETHClient.GetErc20BalanceAsync(this.interopSettings.MultisigWalletAddress).ConfigureAwait(false);

                // The request is denominated in satoshi and needs to be converted to wei.
                BigInteger amountInWei = this.CoinsToWei(Money.Satoshis(request.Amount));

                /* Temporarily disabled mint logic
                 * if (amountInWei > reserveBalanace)
                 * {
                 *  if (this.minting)
                 *  {
                 *      this.logger.LogInformation("Minting transaction has not yet confirmed. Waiting.");
                 *
                 *      return;
                 *  }
                 *
                 *  this.minting = true;
                 *
                 *  this.logger.LogInformation("Insufficient reserve balance remaining, initiating mint transaction to replenish reserve.");
                 *
                 *  string mintData = this.ETHClient.EncodeMintParams(this.interopSettings.MultisigWalletAddress, ReserveBalanceTarget);
                 *
                 *  BigInteger mintTransactionId = await this.ETHClient.SubmitTransactionAsync(request.DestinationAddress, 0, mintData).ConfigureAwait(false);
                 *
                 *  // Now we need to broadcast the mint transactionId to the other multisig nodes so that they can sign it off.
                 *  string mintSignature = this.federationManager.CurrentFederationKey.SignMessage(MintPlaceHolderRequestId + ((int)mintTransactionId));
                 *  // TODO: The other multisig nodes must be careful not to blindly trust that any given transactionId relates to a mint transaction. Need to validate the recipient
                 *  await this.federatedPegBroadcaster.BroadcastAsync(new InteropCoordinationPayload(MintPlaceHolderRequestId, (int)mintTransactionId, mintSignature)).ConfigureAwait(false);
                 *
                 *  return;
                 * }
                 */

                // TODO: Perhaps the transactionId coordination should actually be done within the multisig contract. This will however increase gas costs for each mint. Maybe a Cirrus contract instead?
                switch (request.RequestStatus)
                {
                case ((int)ConversionRequestStatus.Unprocessed):
                {
                    if (originator)
                    {
                        // If this node is the designated transaction originator, it must create and submit the transaction to the multisig.
                        this.logger.LogInformation("This node selected as originator for transaction {0}.", request.RequestId);

                        request.RequestStatus = ConversionRequestStatus.OriginatorNotSubmitted;
                    }
                    else
                    {
                        this.logger.LogInformation("This node was not selected as the originator for transaction {0}. The originator is: {1}.", request.RequestId, designatedMember.PubKey.ToHex());

                        request.RequestStatus = ConversionRequestStatus.NotOriginator;
                    }

                    break;
                }

                case (ConversionRequestStatus.OriginatorNotSubmitted):
                {
                    // First construct the necessary transfer() transaction data, utilising the ABI of the wrapped STRAX ERC20 contract.
                    // When this constructed transaction is actually executed, the transfer's source account will be the account executing the transaction i.e. the multisig contract address.
                    string abiData = clientForDestChain.EncodeTransferParams(request.DestinationAddress, amountInWei);

                    // Submit the unconfirmed transaction data to the multisig contract, returning a transactionId used to refer to it.
                    // Once sufficient multisig owners have confirmed the transaction the multisig contract will execute it.
                    // Note that by submitting the transaction to the multisig wallet contract, the originator is implicitly granting it one confirmation.
                    BigInteger transactionId = await clientForDestChain.SubmitTransactionAsync(this.interopSettings.ETHSettings.WrappedStraxContractAddress, 0, abiData).ConfigureAwait(false);

                    this.logger.LogInformation("Originator submitted transaction to multisig and was allocated transactionId {0}.", transactionId);

                    // TODO: Need to persist vote storage across node shutdowns
                    this.interopTransactionManager.AddVote(request.RequestId, transactionId, this.federationManager.CurrentFederationKey.PubKey);

                    request.RequestStatus = ConversionRequestStatus.OriginatorSubmitted;

                    break;
                }

                case (ConversionRequestStatus.OriginatorSubmitted):
                {
                    // It must then propagate the transactionId to the other nodes so that they know they should confirm it.
                    // The reason why each node doesn't simply maintain its own transaction counter, is that it can't be guaranteed
                    // that a transaction won't be submitted out-of-turn by a rogue or malfunctioning federation multisig node.
                    // The coordination mechanism safeguards against this, as any such spurious transaction will not receive acceptance votes.
                    // TODO: The transactionId should be accompanied by the hash of the submission transaction on the Ethereum chain so that it can be verified

                    BigInteger transactionId2 = this.interopTransactionManager.GetCandidateTransactionId(request.RequestId);

                    if (transactionId2 != BigInteger.MinusOne)
                    {
                        await this.BroadcastCoordinationAsync(request.RequestId, transactionId2, request.DestinationChain).ConfigureAwait(false);

                        BigInteger agreedTransactionId = this.interopTransactionManager.GetAgreedTransactionId(request.RequestId, 6);

                        if (agreedTransactionId != BigInteger.MinusOne)
                        {
                            this.logger.LogInformation("Transaction {0} has received sufficient votes, it should now start getting confirmed by each peer.", agreedTransactionId);

                            request.RequestStatus = ConversionRequestStatus.VoteFinalised;
                        }
                    }

                    break;
                }

                case (ConversionRequestStatus.VoteFinalised):
                {
                    BigInteger transactionId3 = this.interopTransactionManager.GetAgreedTransactionId(request.RequestId, 6);

                    if (transactionId3 != BigInteger.MinusOne)
                    {
                        // The originator isn't responsible for anything further at this point, except for periodically checking the confirmation count.
                        // The non-originators also need to monitor the confirmation count so that they know when to mark the transaction as processed locally.
                        BigInteger confirmationCount = await clientForDestChain.GetConfirmationCountAsync(transactionId3).ConfigureAwait(false);

                        if (confirmationCount >= 6)
                        {
                            this.logger.LogInformation("Transaction {0} has received at least 6 confirmations, it will be automatically executed by the multisig contract.", transactionId3);

                            request.RequestStatus = ConversionRequestStatus.Processed;
                            request.Processed     = true;

                            // We no longer need to track votes for this transaction.
                            this.interopTransactionManager.RemoveTransaction(request.RequestId);
                        }
                        else
                        {
                            this.logger.LogInformation("Transaction {0} has finished voting but does not yet have 8 confirmations, re-broadcasting votes to peers.", transactionId3);

                            // There are not enough confirmations yet.
                            // Even though the vote is finalised, other nodes may come and go. So we re-broadcast the finalised votes to all federation peers.
                            // Nodes will simply ignore the messages if they are not relevant.

                            await this.BroadcastCoordinationAsync(request.RequestId, transactionId3, request.DestinationChain).ConfigureAwait(false);

                            // No state transition here, we are waiting for sufficient confirmations.
                        }
                    }

                    break;
                }

                case (ConversionRequestStatus.NotOriginator):
                {
                    // If not the originator, this node needs to determine what multisig wallet transactionId it should confirm.
                    // Initially there will not be a quorum of nodes that agree on the transactionId.
                    // So each node needs to satisfy itself that the transactionId sent by the originator exists in the multisig wallet.
                    // This is done within the InteropBehavior automatically, we just check each poll loop if a transaction has enough votes yet.
                    // Each node must only ever confirm a single transactionId for a given conversion transaction.
                    BigInteger agreedUponId = this.interopTransactionManager.GetAgreedTransactionId(request.RequestId, 6);

                    if (agreedUponId != BigInteger.MinusOne)
                    {
                        this.logger.LogInformation("Quorum reached for conversion transaction {0} with transactionId {1}, submitting confirmation to contract.", request.RequestId, agreedUponId);

                        // Once a quorum is reached, each node confirms the agreed transactionId.
                        // If the originator or some other nodes renege on their vote, the current node will not re-confirm a different transactionId.
                        string confirmationHash = await clientForDestChain.ConfirmTransactionAsync(agreedUponId).ConfigureAwait(false);

                        this.logger.LogInformation("The hash of the confirmation transaction for conversion transaction {0} was {1}.", request.RequestId, confirmationHash);

                        request.RequestStatus = ConversionRequestStatus.VoteFinalised;
                    }
                    else
                    {
                        BigInteger transactionId4 = this.interopTransactionManager.GetCandidateTransactionId(request.RequestId);

                        if (transactionId4 != BigInteger.MinusOne)
                        {
                            this.logger.LogInformation("Broadcasting vote (transactionId {0}) for conversion transaction {1}.", transactionId4, request.RequestId);

                            this.interopTransactionManager.AddVote(request.RequestId, transactionId4, this.federationManager.CurrentFederationKey.PubKey);

                            await this.BroadcastCoordinationAsync(request.RequestId, transactionId4, request.DestinationChain).ConfigureAwait(false);
                        }

                        // No state transition here, as we are waiting for the candidate transactionId to progress to an agreed upon transactionId via a quorum.
                    }

                    break;
                }
                }

                // Make sure that any state transitions are persisted to storage.
                this.conversionRequestRepository.Save(request);

                // Unlike the mint requests, burns are not initiated by the multisig wallet.
                // Instead they are initiated by the user, via a contract call to the burn() method on the WrappedStrax contract.
                // They need to provide a destination STRAX address when calling the burn method.

                // Properly processing burn transactions requires emulating a withdrawal on the main chain from the multisig wallet.
                // It will be easier when conversion can be done directly to and from a Cirrus contract instead.

                // Currently the processing is done in the WithdrawalExtractor.
            }
        }