/// <summary> /// /// </summary> /// <param name="hotWalletCashout"></param> /// <returns>transaction hash</returns> public async Task <string> StartCashoutAsync(string operationId) { IHotWalletOperation cashout = await _hotWalletCashoutRepository.GetAsync(operationId); if (cashout == null || cashout.OperationType != HotWalletOperationType.Cashout) { await _log.WriteWarningAsync(nameof(HotWalletService), nameof(StartCashoutAsync), $"operationId - {operationId}", "No cashout info for operation", DateTime.UtcNow); return(null); } var gasPrice = await _gasPriceRepository.GetAsync(); var currentGasPriceHex = await _web3.Eth.GasPrice.SendRequestAsync(); var currentGasPrice = currentGasPriceHex.Value; BigInteger selectedGasPrice = currentGasPrice * _baseSettings.GasPricePercentage / 100; if (selectedGasPrice > gasPrice.Max) { selectedGasPrice = gasPrice.Max; } else if (selectedGasPrice < gasPrice.Min) { selectedGasPrice = gasPrice.Min; } string transactionForSigning = null; string signedTransaction = null; string transactionHash = null; bool isErc20Transfer = !string.IsNullOrEmpty(cashout.TokenAddress); SemaphoreSlim semaphore = _semaphores.GetOrAdd(cashout.FromAddress, f => new SemaphoreSlim(1, 1)); try { await semaphore.WaitAsync(); //Erc20 transfer if (isErc20Transfer) { transactionForSigning = await _erc20PrivateWalletService.GetTransferTransactionRaw(new Lykke.Service.EthereumCore.BusinessModels.PrivateWallet.Erc20Transaction() { FromAddress = cashout.FromAddress, GasAmount = Constants.GasForHotWalletTransaction, GasPrice = selectedGasPrice, ToAddress = cashout.ToAddress, TokenAddress = cashout.TokenAddress, TokenAmount = cashout.Amount, Value = 0, }, useTxPool : true); } //Eth transfer else { transactionForSigning = await _privateWalletService.GetTransactionForSigning(new Lykke.Service.EthereumCore.BusinessModels.PrivateWallet.EthTransaction() { FromAddress = cashout.FromAddress, GasAmount = Constants.GasForHotWalletTransaction, GasPrice = selectedGasPrice, ToAddress = cashout.ToAddress, Value = cashout.Amount }, useTxPool : true); } signedTransaction = await _signatureService.SignRawTransactionAsync(cashout.FromAddress, transactionForSigning); if (string.IsNullOrEmpty(signedTransaction)) { throw new ClientSideException(ExceptionType.WrongSign, "Wrong signature"); } var transactionExecutionCosts = await _privateWalletService.EstimateTransactionExecutionCost(cashout.FromAddress, signedTransaction); if (!transactionExecutionCosts.IsAllowed) { throw new Exception($"Transaction will not be successfull {JsonSerialisersExt.ToJson(cashout)}"); } transactionHash = isErc20Transfer ? await _erc20PrivateWalletService.SubmitSignedTransaction(cashout.FromAddress, signedTransaction) : await _privateWalletService.SubmitSignedTransaction(cashout.FromAddress, signedTransaction); } finally { semaphore.Release(); } if (string.IsNullOrEmpty(transactionHash)) { throw new Exception("Transaction was not sent"); } CoinTransactionMessage message = new CoinTransactionMessage() { OperationId = operationId, TransactionHash = transactionHash }; await _hotWalletCashoutTransactionRepository.SaveAsync(new HotWalletCashoutTransaction() { OperationId = operationId, TransactionHash = transactionHash }); await _hotWalletTransactionMonitoringQueue.PutRawMessageAsync(Newtonsoft.Json.JsonConvert.SerializeObject(message)); return(transactionHash); }