public async Task <IActionResult> GetIdentityVerificationPolicy(string address)
        {
            // validate address
            if (!AddressParser.IsValidAddress(address, _network))
            {
                return(StatusCode(StatusCodes.Status400BadRequest, $"Invalid address {address}"));
            }

            // check contract exists
            var numericAddress = address.ToUint160(_network);

            if (!_stateRepositoryRoot.IsExist(numericAddress))
            {
                return(StatusCode(StatusCodes.Status404NotFound, $"No smart contract found at address {address}"));
            }

            var requiresIdentityVerification = RetrieveIdentityVerificationPolicy(numericAddress);

            return(Ok(requiresIdentityVerification));
        }
        public async Task <IActionResult> GetTickets(string address)
        {
            // validate address
            if (!AddressParser.IsValidAddress(address, _network))
            {
                return(StatusCode(StatusCodes.Status400BadRequest, $"Invalid address {address}"));
            }

            // check contract exists
            var numericAddress = address.ToUint160(_network);

            if (!_stateRepositoryRoot.IsExist(numericAddress))
            {
                return(StatusCode(StatusCodes.Status404NotFound, $"No smart contract found at address {address}"));
            }

            // retrieve value
            var tickets = RetrieveTickets(numericAddress);

            return(Ok(tickets));
        }
        public async Task <IActionResult> GetTicketReleaseFee(string address)
        {
            // validate address
            if (!AddressParser.IsValidAddress(address, _network))
            {
                return(StatusCode(StatusCodes.Status400BadRequest, $"Invalid address {address}"));
            }

            // check contract exists
            var numericAddress = address.ToUint160(_network);

            if (!_stateRepositoryRoot.IsExist(numericAddress))
            {
                return(StatusCode(StatusCodes.Status404NotFound, $"No smart contract found at address {address}"));
            }

            // retrieve value
            var serializedValue = _stateRepositoryRoot.GetStorageValue(numericAddress, _serializer.Serialize("ReleaseFee"));

            return(Ok(_serializer.ToUInt64(serializedValue)));
        }
        public async Task <IActionResult> EndSale(string address, EndSaleRequest endSaleRequest)
        {
            // validate address
            if (!AddressParser.IsValidAddress(address, _network))
            {
                return(StatusCode(StatusCodes.Status400BadRequest, $"Invalid address {address}"));
            }

            // check contract exists
            var numericAddress = address.ToUint160(_network);

            if (!_stateRepositoryRoot.IsExist(numericAddress))
            {
                return(StatusCode(StatusCodes.Status404NotFound, $"No smart contract found at address {address}"));
            }

            if (!HasSaleEnded(numericAddress))
            {
                return(StatusCode(StatusCodes.Status409Conflict, "Sale is not currently active or has not ended"));
            }

            // check connections
            if (!_connectionManager.ConnectedPeers.Any())
            {
                _logger.LogTrace("No connected peers");
                return(StatusCode(StatusCodes.Status403Forbidden, "Can't send transaction as the node requires at least one connection."));
            }

            // build transaction
            var callTxResponse = _smartContractTransactionService.BuildCallTx(new BuildCallContractTransactionRequest
            {
                AccountName     = endSaleRequest.AccountName,
                Amount          = "0",
                ContractAddress = address,
                FeeAmount       = "0",
                GasLimit        = SmartContractFormatLogic.GasLimitMaximum,
                GasPrice        = endSaleRequest.GasPrice,
                MethodName      = nameof(TicketContract.EndSale),
                Outpoints       = endSaleRequest.Outpoints,
                Parameters      = Array.Empty <string>(),
                Password        = endSaleRequest.Password,
                Sender          = endSaleRequest.Sender,
                WalletName      = endSaleRequest.WalletName,
            });

            if (!callTxResponse.Success)
            {
                return(StatusCode(StatusCodes.Status400BadRequest, callTxResponse.Message));
            }

            // broadcast transaction
            var transaction = _network.CreateTransaction(callTxResponse.Hex);
            await _broadcasterManager.BroadcastTransactionAsync(transaction);

            var transactionBroadCastEntry = _broadcasterManager.GetTransaction(transaction.GetHash()); // check if transaction was added to mempool

            if (transactionBroadCastEntry?.State == Stratis.Bitcoin.Features.Wallet.Broadcasting.State.CantBroadcast)
            {
                _logger.LogError("Exception occurred: {0}", transactionBroadCastEntry.ErrorMessage);
                return(StatusCode(StatusCodes.Status500InternalServerError, transactionBroadCastEntry.ErrorMessage));
            }

            var transactionHash = transaction.GetHash().ToString();

            return(Created($"/api/smartContracts/receipt?txHash={transactionHash}", transactionHash));
        }
        public async Task <IActionResult> BeginSale(string address, BeginSaleRequest beginSaleRequest)
        {
            // validate address
            if (!AddressParser.IsValidAddress(address, _network))
            {
                return(StatusCode(StatusCodes.Status400BadRequest, $"Invalid address {address}"));
            }

            // check end of sale is in future
            if (beginSaleRequest.Details.EndOfSale <= (ulong)_consensusManager.Tip.Height)
            {
                return(StatusCode(StatusCodes.Status400BadRequest, $"End of sale must be in the future. Consensus height is at {_consensusManager.Tip.Height}"));
            }

            // check contract exists
            var numericAddress = address.ToUint160(_network);

            if (!_stateRepositoryRoot.IsExist(numericAddress))
            {
                return(StatusCode(StatusCodes.Status404NotFound, $"No smart contract found at address {address}"));
            }

            if (HasActiveSale(numericAddress))
            {
                return(StatusCode(StatusCodes.Status409Conflict, "Sale is currently active"));
            }

            // check connections
            if (!_connectionManager.ConnectedPeers.Any())
            {
                _logger.LogTrace("No connected peers");
                return(StatusCode(StatusCodes.Status403Forbidden, "Can't send transaction as the node requires at least one connection."));
            }

            // retrieve tickets
            var serializedTickets = _stateRepositoryRoot.GetStorageValue(numericAddress, _serializer.Serialize(nameof(TicketContract.Tickets)));
            var tickets           = _serializer.ToArray <Ticket>(serializedTickets);

            if (beginSaleRequest.SeatPrices.Length != tickets.Length)
            {
                return(StatusCode(StatusCodes.Status400BadRequest, "Must supply prices for all seats"));
            }

            foreach (var seatPrice in beginSaleRequest.SeatPrices)
            {
                for (int i = 0; i < tickets.Length; i++)
                {
                    if (tickets[i].Seat.Equals(seatPrice.Seat))
                    {
                        tickets[i].Price = seatPrice.Price;
                    }
                }
            }

            var ticketsParameter   = $"{Serialization.TypeIdentifiers[typeof(byte[])]}#{Serialization.ByteArrayToHex(_serializer.Serialize(tickets))}";
            var showNameParameter  = $"{Serialization.TypeIdentifiers[typeof(string)]}#{beginSaleRequest.Details.Name}";
            var organiserParameter = $"{Serialization.TypeIdentifiers[typeof(string)]}#{beginSaleRequest.Details.Organiser}";
            var timeParameter      = $"{Serialization.TypeIdentifiers[typeof(ulong)]}#{beginSaleRequest.Details.Time}";
            var endOfSaleParameter = $"{Serialization.TypeIdentifiers[typeof(ulong)]}#{beginSaleRequest.Details.EndOfSale}";

            // build transaction
            var callTxResponse = _smartContractTransactionService.BuildCallTx(new BuildCallContractTransactionRequest
            {
                AccountName     = beginSaleRequest.AccountName,
                Amount          = "0",
                ContractAddress = address,
                FeeAmount       = "0",
                GasLimit        = SmartContractFormatLogic.GasLimitMaximum,
                GasPrice        = beginSaleRequest.GasPrice,
                MethodName      = nameof(TicketContract.BeginSale),
                Outpoints       = beginSaleRequest.Outpoints,
                Parameters      = new string[] { ticketsParameter, showNameParameter, organiserParameter, timeParameter, endOfSaleParameter },
                Password        = beginSaleRequest.Password,
                Sender          = beginSaleRequest.Sender,
                WalletName      = beginSaleRequest.WalletName,
            });

            if (!callTxResponse.Success)
            {
                return(StatusCode(StatusCodes.Status400BadRequest, callTxResponse.Message));
            }

            // broadcast transaction
            var transaction = _network.CreateTransaction(callTxResponse.Hex);
            await _broadcasterManager.BroadcastTransactionAsync(transaction);

            var transactionBroadCastEntry = _broadcasterManager.GetTransaction(transaction.GetHash()); // check if transaction was added to mempool

            if (transactionBroadCastEntry?.State == Stratis.Bitcoin.Features.Wallet.Broadcasting.State.CantBroadcast)
            {
                _logger.LogError("Exception occurred: {0}", transactionBroadCastEntry.ErrorMessage);
                return(StatusCode(StatusCodes.Status500InternalServerError, transactionBroadCastEntry.ErrorMessage));
            }

            var transactionHash = transaction.GetHash().ToString();

            return(Created($"/api/smartContracts/receipt?txHash={transactionHash}", transactionHash));
        }
        public async Task <IActionResult> ReleaseTicket(string address, ReleaseTicketRequest releaseTicketRequest)
        {
            // validate address
            if (!AddressParser.IsValidAddress(address, _network))
            {
                return(StatusCode(StatusCodes.Status400BadRequest, $"Invalid address {address}"));
            }

            // check contract exists
            var numericAddress = address.ToUint160(_network);

            if (!_stateRepositoryRoot.IsExist(numericAddress))
            {
                return(StatusCode(StatusCodes.Status404NotFound, $"No smart contract found at address {address}"));
            }

            // check for state of ticket
            var ticket = FindTicket(numericAddress, releaseTicketRequest.Seat);

            if (ticket.Equals(default(Ticket)))
            {
                return(StatusCode(StatusCodes.Status400BadRequest, $"Invalid seat {releaseTicketRequest.Seat.ToDisplayString()}"));
            }

            // check state of contract
            if (!HasOpenSale(numericAddress))
            {
                return(StatusCode(StatusCodes.Status409Conflict, "Sale is not currently open"));
            }

            if (!IsRefundAvailable(numericAddress))
            {
                return(StatusCode(StatusCodes.Status409Conflict, "Ticket release is no longer available"));
            }

            // verify ownership
            if (ticket.Address == Address.Zero || ticket.Address != releaseTicketRequest.Sender.ToAddress(_network))
            {
                return(StatusCode(StatusCodes.Status400BadRequest, $"Ticket for seat {releaseTicketRequest.Seat.ToDisplayString()} not owned by {releaseTicketRequest.Sender}"));
            }

            // check connections
            if (!_connectionManager.ConnectedPeers.Any())
            {
                _logger.LogTrace("No connected peers");
                return(StatusCode(StatusCodes.Status403Forbidden, "Can't send transaction as the node requires at least one connection."));
            }

            var seatBytes     = _serializer.Serialize(releaseTicketRequest.Seat);
            var seatParameter = $"{Serialization.TypeIdentifiers[typeof(byte[])]}#{Serialization.ByteArrayToHex(seatBytes)}";

            // build transaction
            var callTxResponse = _smartContractTransactionService.BuildCallTx(new BuildCallContractTransactionRequest
            {
                AccountName     = releaseTicketRequest.AccountName,
                Amount          = "0",
                ContractAddress = address,
                FeeAmount       = "0",
                GasLimit        = SmartContractFormatLogic.GasLimitMaximum,
                GasPrice        = releaseTicketRequest.GasPrice,
                MethodName      = nameof(TicketContract.ReleaseTicket),
                Outpoints       = releaseTicketRequest.Outpoints,
                Parameters      = new string[] { seatParameter },
                Password        = releaseTicketRequest.Password,
                Sender          = releaseTicketRequest.Sender,
                WalletName      = releaseTicketRequest.WalletName,
            });

            if (!callTxResponse.Success)
            {
                return(StatusCode(StatusCodes.Status400BadRequest, callTxResponse.Message));
            }

            // broadcast transaction
            var transaction = _network.CreateTransaction(callTxResponse.Hex);
            await _broadcasterManager.BroadcastTransactionAsync(transaction);

            var transactionBroadCastEntry = _broadcasterManager.GetTransaction(transaction.GetHash()); // check if transaction was added to mempool

            if (transactionBroadCastEntry?.State == Stratis.Bitcoin.Features.Wallet.Broadcasting.State.CantBroadcast)
            {
                _logger.LogError("Exception occurred: {0}", transactionBroadCastEntry.ErrorMessage);
                return(StatusCode(StatusCodes.Status500InternalServerError, transactionBroadCastEntry.ErrorMessage));
            }

            var transactionHash = transaction.GetHash().ToString();

            return(Created($"/api/smartContracts/receipt?txHash={transactionHash}", transactionHash));
        }
        public async Task <IActionResult> ReserveTicket(string address, ReserveTicketRequest reserveTicketRequest)
        {
            // validate address
            if (!AddressParser.IsValidAddress(address, _network))
            {
                return(StatusCode(StatusCodes.Status400BadRequest, $"Invalid address {address}"));
            }

            // check contract exists
            var numericAddress = address.ToUint160(_network);

            if (!_stateRepositoryRoot.IsExist(numericAddress))
            {
                return(StatusCode(StatusCodes.Status404NotFound, $"No smart contract found at address {address}"));
            }

            // check for state of ticket
            var ticket = FindTicket(numericAddress, reserveTicketRequest.Seat);

            if (ticket.Equals(default(Ticket)))
            {
                return(StatusCode(StatusCodes.Status400BadRequest, $"Invalid seat {reserveTicketRequest.Seat.ToDisplayString()}"));
            }

            // check contract state
            if (!HasOpenSale(numericAddress))
            {
                return(StatusCode(StatusCodes.Status409Conflict, "Sale is not currently open"));
            }

            if (ticket.Address != Address.Zero)
            {
                return(StatusCode(StatusCodes.Status400BadRequest, $"Ticket for seat {reserveTicketRequest.Seat.ToDisplayString()} not available to purchase"));
            }

            var requiresIdentityVerification = RetrieveIdentityVerificationPolicy(numericAddress);

            if (requiresIdentityVerification && string.IsNullOrWhiteSpace(reserveTicketRequest.CustomerName))
            {
                return(StatusCode(StatusCodes.Status400BadRequest, $"Customer name is required"));
            }

            // check connections
            if (!_connectionManager.ConnectedPeers.Any())
            {
                _logger.LogTrace("No connected peers");
                return(StatusCode(StatusCodes.Status403Forbidden, "Can't send transaction as the node requires at least one connection."));
            }

            var seatBytes     = _serializer.Serialize(reserveTicketRequest.Seat);
            var seatParameter = $"{Serialization.TypeIdentifiers[typeof(byte[])]}#{Serialization.ByteArrayToHex(seatBytes)}";

            var       secret = _stringGenerator.CreateUniqueString(15);
            CbcResult secretCipherResult;

            using (var cipherProvider = _cipherFactory.CreateCbcProvider())
            {
                secretCipherResult = cipherProvider.Encrypt(secret);
            }

            var secretParameter = $"{Serialization.TypeIdentifiers[typeof(byte[])]}#{Serialization.ByteArrayToHex(secretCipherResult.Cipher)}";

            CbcResult customerNameCipherResult = null;
            string    customerNameParameter    = null;

            if (requiresIdentityVerification)
            {
                using (var cipherProvider = _cipherFactory.CreateCbcProvider())
                {
                    customerNameCipherResult = cipherProvider.Encrypt(reserveTicketRequest.CustomerName);
                }

                customerNameParameter = $"{Serialization.TypeIdentifiers[typeof(byte[])]}#{Serialization.ByteArrayToHex(customerNameCipherResult.Cipher)}";
            }

            // build transaction
            var parameterList = new List <string> {
                seatParameter, secretParameter
            };

            if (customerNameParameter != null)
            {
                parameterList.Add(customerNameParameter);
            }

            var callTxResponse = _smartContractTransactionService.BuildCallTx(new BuildCallContractTransactionRequest
            {
                AccountName     = reserveTicketRequest.AccountName,
                Amount          = StratoshisToStrats(ticket.Price),
                ContractAddress = address,
                FeeAmount       = "0",
                GasLimit        = SmartContractFormatLogic.GasLimitMaximum,
                GasPrice        = reserveTicketRequest.GasPrice,
                MethodName      = nameof(TicketContract.Reserve),
                Outpoints       = reserveTicketRequest.Outpoints,
                Parameters      = parameterList.ToArray(),
                Password        = reserveTicketRequest.Password,
                Sender          = reserveTicketRequest.Sender,
                WalletName      = reserveTicketRequest.WalletName,
            });

            if (!callTxResponse.Success)
            {
                return(StatusCode(StatusCodes.Status400BadRequest, callTxResponse.Message));
            }

            // broadcast transaction
            var transaction = _network.CreateTransaction(callTxResponse.Hex);
            await _broadcasterManager.BroadcastTransactionAsync(transaction);

            var transactionBroadCastEntry = _broadcasterManager.GetTransaction(transaction.GetHash()); // check if transaction was added to mempool

            if (transactionBroadCastEntry?.State == Stratis.Bitcoin.Features.Wallet.Broadcasting.State.CantBroadcast)
            {
                _logger.LogError("Exception occurred: {0}", transactionBroadCastEntry.ErrorMessage);
                return(StatusCode(StatusCodes.Status500InternalServerError, transactionBroadCastEntry.ErrorMessage));
            }

            var transactionHash = transaction.GetHash().ToString();
            var cbcSecretValues = new CbcSecret
            {
                Plaintext = secret,
                Key       = secretCipherResult.Key,
                IV        = secretCipherResult.IV
            };
            var cbcCustomerValues = requiresIdentityVerification
                ? new CbcValues
            {
                Key = customerNameCipherResult.Key,
                IV  = customerNameCipherResult.IV
            }
                : null;

            return(Created(
                       $"/api/smartContracts/receipt?txHash={transactionHash}",
                       new TicketReservationDetailsResponse
            {
                TransactionHash = transactionHash,
                Secret = cbcSecretValues,
                CustomerName = cbcCustomerValues
            }));
        }