public async Task Execute(LykkePayErc20TransferMessage transaction, QueueTriggeringContext context) { IHotWalletOperation operation = null; if (string.IsNullOrEmpty(transaction?.OperationId)) { await _logger.WriteWarningAsync(nameof(Erc20DepositTransferStarterJob), "Execute", "", "Empty message skipped"); return; } try { operation = await _operationsRepository.GetAsync(transaction.OperationId); if (operation == null) { await _logger.WriteWarningAsync(nameof(Erc20DepositTransferStarterJob), "Execute", transaction.ToJson(), $"No operation for id {transaction?.OperationId} message skipped"); return; } var transactionSenderAddress = _settings.Airlines.AirlinesAddress; var balance = await _ercInterfaceService.GetPendingBalanceForExternalTokenAsync(operation.FromAddress, operation.TokenAddress); if (balance < operation.Amount) { await _logger.WriteWarningAsync(nameof(Erc20DepositTransferStarterJob), "Execute", transaction.ToJson(), $"Sendig Failed Event: DepositAddress: {operation.FromAddress}, " + $"TokenAddress: {operation.TokenAddress}, " + $"DesiredAmount: {operation.Amount} " + $"CurrentBalance {balance}"); TransferEvent @event = new TransferEvent(transaction.OperationId, "", operation.Amount.ToString(), operation.TokenAddress, operation.FromAddress, operation.ToAddress, "", 0, SenderType.EthereumCore, EventType.NotEnoughFunds, WorkflowType.Airlines, DateTime.UtcNow); await _rabbitQueuePublisher.PublshEvent(@event); return; } var trHash = await Erc223SharedService.StartDepositTransferAsync(_web3, _settings.EthereumCore.Erc223DepositContract.Abi, transactionSenderAddress, operation.FromAddress, operation.TokenAddress, operation.ToAddress, operation.Amount); await _hotWalletTransactionRepository.SaveAsync(new HotWalletCashoutTransaction() { OperationId = transaction.OperationId, TransactionHash = trHash }); var message = new CoinTransactionMessage() { OperationId = transaction.OperationId, TransactionHash = trHash }; //Observe transaction await _transactionMonitoringQueue.PutRawMessageAsync(message.ToJson()); var notificationMessage = new LykkePayErc20TransferNotificationMessage() { OperationId = transaction.OperationId, TransactionHash = trHash, Balance = operation.Amount.ToString() }; await _transactionStartedNotificationQueue.PutRawMessageAsync(notificationMessage.ToJson()); } catch (ClientSideException ex) { if (operation == null) { return; } TransferEvent @event = new TransferEvent(transaction.OperationId, "", operation.Amount.ToString(), operation.TokenAddress, operation.FromAddress, operation.ToAddress, "", 0, SenderType.EthereumCore, EventType.Failed, WorkflowType.Airlines, DateTime.UtcNow); _logger.WriteWarning("Execute", operation.ToJson(), "ClientSideException", ex); await _rabbitQueuePublisher.PublshEvent(@event); } catch (Exception ex) { if (transaction == null) { return; } if (ex.Message != transaction.LastError) { _logger.WriteWarning("Execute", transaction.ToJson(), $"{transaction.OperationId}"); } transaction.LastError = ex.Message; transaction.DequeueCount++; context.MoveMessageToEnd(transaction.ToJson()); context.SetCountQueueBasedDelay(_settings.EthereumCore.MaxQueueDelay, 200); _logger.WriteError("Execute", transaction.ToJson(), ex); } }
/// <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); }
public async Task Execute(LykkePayErc20TransferMessage transaction, QueueTriggeringContext context) { IHotWalletOperation operation = null; if (string.IsNullOrEmpty(transaction?.OperationId)) { await _logger.WriteWarningAsync(nameof(LykkePayErc20DepositTransferStarterJob), "Execute", "", "Empty message skipped"); return; } try { operation = await _operationsRepository.GetAsync(transaction.OperationId); if (operation == null) { await _logger.WriteWarningAsync(nameof(LykkePayErc20DepositTransferStarterJob), "Execute", transaction.ToJson(), $"No operation for id {transaction?.OperationId} message skipped"); return; } var transactionSenderAddress = _settings.LykkePay.LykkePayAddress; var balance = await _ercInterfaceService.GetPendingBalanceForExternalTokenAsync(operation.FromAddress, operation.TokenAddress); if (balance == 0) { await _logger.WriteWarningAsync(nameof(LykkePayErc20DepositTransferStarterJob), "Execute", transaction.ToJson(), $"DepositAddress: {operation.FromAddress}, TokenAddress: {operation.TokenAddress}"); //TODO: Transaction Failed return; } var trHash = await Erc20SharedService.StartDepositTransferAsync(_web3, _settings.EthereumCore, transactionSenderAddress, operation.FromAddress, operation.TokenAddress, operation.ToAddress); await _hotWalletTransactionRepository.SaveAsync(new HotWalletCashoutTransaction() { OperationId = transaction.OperationId, TransactionHash = trHash }); var message = new CoinTransactionMessage() { OperationId = transaction.OperationId, TransactionHash = trHash }; //Observe transaction await _transactionMonitoringQueue.PutRawMessageAsync( Newtonsoft.Json.JsonConvert.SerializeObject(message)); var notificationMessage = new LykkePayErc20TransferNotificationMessage() { OperationId = transaction.OperationId, TransactionHash = trHash, Balance = balance.ToString() //At the starting moment(may change at the end of the execution) }; await _transactionStartedNotificationQueue.PutRawMessageAsync( Newtonsoft.Json.JsonConvert.SerializeObject(notificationMessage)); } catch (ClientSideException ex) { if (operation == null) { return; } TransferEvent @event = new TransferEvent(transaction.OperationId, "", operation.Amount.ToString(), operation.TokenAddress, operation.FromAddress, operation.ToAddress, "", 0, SenderType.EthereumCore, EventType.Failed, WorkflowType.LykkePay, DateTime.UtcNow); await _logger.WriteWarningAsync(nameof(LykkePayErc20DepositTransferStarterJob), "Execute", operation.ToJson(), ex); await _rabbitQueuePublisher.PublshEvent(@event); } catch (Exception ex) { if (transaction == null) { return; } if (ex.Message != transaction.LastError) { await _logger.WriteWarningAsync(nameof(LykkePayErc20DepositTransferStarterJob), "Execute", transaction.ToJson(), "transaction.OperationId"); } transaction.LastError = ex.Message; transaction.DequeueCount++; context.MoveMessageToEnd(transaction.ToJson()); context.SetCountQueueBasedDelay(_settings.EthereumCore.MaxQueueDelay, 200); await _logger.WriteErrorAsync(nameof(LykkePayErc20DepositTransferStarterJob), "Execute", transaction.ToJson(), ex); } }