public async Task <CommandHandlingResult> Handle(CreateTradeCommand command, IEventPublisher eventPublisher) { var queueMessage = command.QueueMessage; var clientId = queueMessage.Order.ClientId; if (!queueMessage.Order.Status.Equals("matched", StringComparison.OrdinalIgnoreCase)) { _log.Info($"{nameof(TradeSaga)}:{nameof(TradeCommandHandler)}", "Message processing being aborted, due to order status is not matched", queueMessage.ToJson()); return(CommandHandlingResult.Ok()); } var context = await _transactionService.GetTransactionContext <SwapOffchainContextData>(queueMessage.Order.Id) ?? new SwapOffchainContextData(); await _contextFactory.FillTradeContext(context, queueMessage.Order, queueMessage.Trades, clientId); ChaosKitty.Meow(); await _transactionService.SetTransactionContext(queueMessage.Order.Id, context); ChaosKitty.Meow(); eventPublisher.PublishEvent(new TradeCreatedEvent { OrderId = queueMessage.Order.Id, IsTrustedClient = context.IsTrustedClient, MarketOrder = context.Order, ClientTrades = context.ClientTrades, QueueMessage = queueMessage }); return(CommandHandlingResult.Ok()); }
public async Task <CommandHandlingResult> Handle(CreateOffchainCashoutRequestCommand command, IEventPublisher eventPublisher) { var sw = new Stopwatch(); sw.Start(); try { await _offchainRequestService.CreateOffchainRequestAndNotify( transactionId : command.Id, clientId : command.ClientId, assetId : command.AssetId, amount : command.Amount, orderId : null, type : OffchainTransferType.TrustedCashout); ChaosKitty.Meow(); return(CommandHandlingResult.Ok()); } finally { sw.Stop(); _log.Info("Command execution time", context: new { TxHandler = new { Handler = nameof(OffchainCommandHandler), Command = nameof(CreateOffchainCashoutRequestCommand), Time = sw.ElapsedMilliseconds } }); } }
private async Task Handle(TransactionProcessedEvent evt, ICommandSender sender) { ChaosKitty.Meow(); var clientAcc = await _clientAccountClient.GetByIdAsync(evt.ClientId); var sendEmailCommand = new SendNoRefundDepositDoneMailCommand { Email = clientAcc.Email, Amount = evt.Amount, AssetId = evt.Asset.Id }; sender.SendCommand(sendEmailCommand, "email"); ChaosKitty.Meow(); var pushSettings = await _clientAccountClient.GetPushNotificationAsync(evt.ClientId); if (pushSettings.Enabled) { var sendNotificationCommand = new SendNotificationCommand { NotificationId = clientAcc.NotificationsId, Type = NotificationType.TransactionConfirmed, Message = string.Format(TextResources.CashInSuccessText, new decimal(evt.Amount).TruncateDecimalPlaces(evt.Asset.Accuracy), evt.Asset.Id) }; sender.SendCommand(sendNotificationCommand, "notifications"); } }
public async Task <CommandHandlingResult> Handle(ProcessCashInCommand command, IEventPublisher eventPublisher) { var id = command.CommandId; var asset = command.Asset; var amount = command.Amount; var transaction = command.Transaction; ChaosKitty.Meow(); var responseModel = await _matchingEngineClient.CashInOutAsync(id, transaction.ClientId, asset.Id, amount.TruncateDecimalPlaces(asset.Accuracy)); if (responseModel.Status != MeStatusCodes.Ok && responseModel.Status != MeStatusCodes.AlreadyProcessed && responseModel.Status != MeStatusCodes.Duplicate) { _log.WriteInfo(nameof(ProcessCashInCommand), command, responseModel.ToJson()); throw new ProcessingException(responseModel.ToJson()); } ChaosKitty.Meow(); eventPublisher.PublishEvent(new TransactionProcessedEvent { ClientId = command.Transaction.ClientId, Asset = command.Asset, Amount = command.Amount }); return(CommandHandlingResult.Ok()); }
public async Task <CommandHandlingResult> Handle(Commands.SegwitTransferCommand command) { var sw = new Stopwatch(); sw.Start(); try { var response = await _bitcoinApiClient.SegwitTransfer(Guid.Parse(command.Id), command.Address); if (response.HasError && response.Error.ErrorCode != ErrorCode.DuplicateTransactionId) { _log.Error($"{nameof(BitcoinCommandHandler)}:{nameof(Commands.SegwitTransferCommand)}", new Exception(response.ToJson()), context: command.ToJson()); return(CommandHandlingResult.Fail(_retryTimeout)); } ChaosKitty.Meow(); return(CommandHandlingResult.Ok()); } finally { sw.Stop(); _log.Info("Command execution time", context: new { TxHandler = new { Handler = nameof(BitcoinCommandHandler), Command = nameof(Commands.SegwitTransferCommand), Time = sw.ElapsedMilliseconds } }); } }
public async Task <CommandHandlingResult> Handle(UpdateLimitOrdersCountCommand command, IEventPublisher eventPublisher) { var sw = new Stopwatch(); sw.Start(); try { var activeLimitOrdersCount = await _limitOrdersRepository.GetActiveOrdersCountAsync(command.ClientId); await _clientCacheRepository.UpdateLimitOrdersCount(command.ClientId, activeLimitOrdersCount); ChaosKitty.Meow(); _log.Info(nameof(UpdateLimitOrdersCountCommand), $"Client {command.ClientId}. Limit orders cache updated: {activeLimitOrdersCount} active orders"); return(CommandHandlingResult.Ok()); } finally { sw.Stop(); _log.Info("Command execution time", context: new { TxHandler = new { Handler = nameof(HistoryCommandHandler), Command = nameof(UpdateLimitOrdersCountCommand), Time = sw.ElapsedMilliseconds } }); } }
public async Task <CommandHandlingResult> Handle(SaveEthInHistoryCommand command, IEventPublisher eventPublisher) { var sw = new Stopwatch(); sw.Start(); try { var cashinId = command.CashinOperationId.ToString("N"); var clientId = command.ClientId; var hash = command.TransactionHash; var amount = command.Amount; var clientAddress = command.ClientAddress; await _cashOperationsRepositoryClient.RegisterAsync(new CashInOutOperation { Id = cashinId, ClientId = clientId.ToString(), AssetId = command.AssetId, Amount = (double)amount, BlockChainHash = hash, DateTime = DateTime.UtcNow, AddressTo = clientAddress, State = TransactionStates.SettledOnchain }); ChaosKitty.Meow(); var clientAcc = await _clientAccountClient.GetByIdAsync(clientId.ToString()); var clientEmail = await _personalDataService.GetEmailAsync(clientId.ToString()); await _srvEmailsFacade.SendNoRefundDepositDoneMail(clientAcc.PartnerId, clientEmail, amount, command.AssetId); await _paymentTransactionsRepository.SetStatus(hash, PaymentStatus.NotifyProcessed); ChaosKitty.Meow(); eventPublisher.PublishEvent(new EthCashinSavedInHistoryEvent() { TransactionHash = hash }); return(CommandHandlingResult.Ok()); } catch (Exception e) { _log.Error(nameof(SaveEthInHistoryCommand), e, context: command); throw; } finally { sw.Stop(); _log.Info("Command execution time", context: new { TxHandler = new { Handler = nameof(EthereumCoreCommandHandler), Command = nameof(SaveEthInHistoryCommand), Time = sw.ElapsedMilliseconds } }); } }
public async Task <CommandHandlingResult> Handle(SendNotificationCommand command) { ChaosKitty.Meow(); await _appNotifications.SendTextNotificationAsync(new [] { command.NotificationId }, command.Type, command.Message); return(CommandHandlingResult.Ok()); }
public async Task <CommandHandlingResult> Handle(CreateTransactionCommand command) { await _transactionsRepository.TryCreateAsync(command.OrderId, BitCoinCommands.SwapOffchain, "", null, ""); ChaosKitty.Meow(); return(CommandHandlingResult.Ok()); }
public async Task <CommandHandlingResult> Handle(SavePostponedCashInCommand command) { await _postponedCashInRepository.SaveAsync(command.TransactionHash); ChaosKitty.Meow(); return(CommandHandlingResult.Ok()); }
public async Task Handle(TradeCreatedEvent evt) { if (evt.ClientTrades != null) { await _clientTradesRepository.SaveAsync(evt.ClientTrades); } ChaosKitty.Meow(); }
public async Task Handle(TransferProcessedEvent evt) { ChaosKitty.Meow(); await _paymentTransactionEventsLog.WriteAsync(PaymentTransactionLogEvent.Create( transactionId: evt.TransferId, techData: "", message: "Confirmed", who: "Tx Detector")); }
private async Task RegisterOperation(CashInOutOperation operation) { var operationId = await _cashOperationsRepositoryClient.RegisterAsync(operation); if (operationId != operation.Id) { _log.Warning($"Unexpected response from Operations Service: {operationId}", context: operation.ToJson()); } ChaosKitty.Meow(); }
public async Task <CommandHandlingResult> Handle(UpdateLimitOrdersCountCommand command, IEventPublisher eventPublisher) { var activeLimitOrdersCount = await _limitOrdersRepository.GetActiveOrdersCountAsync(command.ClientId); await _clientCacheRepository.UpdateLimitOrdersCount(command.ClientId, activeLimitOrdersCount); ChaosKitty.Meow(); _log.Info(nameof(UpdateLimitOrdersCountCommand), $"Client {command.ClientId}. Limit orders cache updated: {activeLimitOrdersCount} active orders"); return(CommandHandlingResult.Ok()); }
private async Task RegisterOperation(TransferEvent operation) { var response = await _transferEventsRepositoryClient.RegisterAsync(operation); if (response.Id != operation.Id) { _log.Warning($"Unexpected response from Operations Service: {response.ToJson()}", context: operation.ToJson()); } ChaosKitty.Meow(); }
public async Task <CommandHandlingResult> Handle(SendNoRefundDepositDoneMailCommand command) { ChaosKitty.Meow(); var content = new NoRefundDepositDoneData { Amount = command.Amount, AssetBcnId = command.AssetId }; await _emailSender.SendEmailAsync(command.Email, content); return(CommandHandlingResult.Ok()); }
private async Task SaveState(BaseCommand command, BaseContextData context) { var transactionId = command.TransactionId.ToString(); var requestData = command.ToJson(); await _transactionsRepository.UpdateAsync(transactionId, requestData, null, ""); ChaosKitty.Meow(); await _transactionService.SetTransactionContext(transactionId, context); ChaosKitty.Meow(); }
public async Task <CommandHandlingResult> Handle(ProcessTransferCommand command, IEventPublisher eventPublisher) { ChaosKitty.Meow(); if (await _paymentTransactionsRepository.SetStatus(command.TransferId, PaymentStatus.NotifyProcessed) != null) { eventPublisher.PublishEvent(new TransferProcessedEvent { TransferId = command.TransferId }); } return(CommandHandlingResult.Ok()); }
private async Task ProcessOutcomeOperation(ProcessEthCoinEventCommand queueMessage) { var transferTx = await _ethereumTransactionRequestRepository.GetAsync(Guid.Parse(queueMessage.OperationId)); CashOutContextData context = await _transactionService.GetTransactionContext <CashOutContextData>(queueMessage.OperationId); if (transferTx != null) { switch (transferTx.OperationType) { case OperationType.CashOut: await SetCashoutHashes(transferTx, queueMessage.TransactionHash); break; case OperationType.Trade: await SetTradeHashes(transferTx, queueMessage.TransactionHash); break; case OperationType.TransferToTrusted: case OperationType.TransferFromTrusted: await SetTransferHashes(transferTx, queueMessage.TransactionHash); break; } ChaosKitty.Meow(); return; } if (context == null) { return; } string clientId = context.ClientId; string hash = queueMessage.TransactionHash; string cashOperationId = context.CashOperationId; var clientAcc = await _clientAccountClient.GetByIdAsync(clientId); var clientEmail = await _personalDataService.GetEmailAsync(clientId); await _cashOperationsRepositoryClient.UpdateBlockchainHashAsync(clientId, cashOperationId, hash); await _srvEmailsFacade.SendNoRefundOCashOutMail(clientAcc.PartnerId, clientEmail, context.Amount, context.AssetId, hash); ChaosKitty.Meow(); }
public async Task <CommandHandlingResult> Handle(CreateOffchainCashoutRequestCommand command, IEventPublisher eventPublisher) { await _offchainRequestService.CreateOffchainRequestAndNotify( transactionId : command.Id, clientId : command.ClientId, assetId : command.AssetId, amount : command.Amount, orderId : null, type : OffchainTransferType.TrustedCashout); ChaosKitty.Meow(); return(CommandHandlingResult.Ok()); }
public async Task <CommandHandlingResult> Handle(Commands.SegwitTransferCommand command) { var response = await _bitcoinApiClient.SegwitTransfer(Guid.Parse(command.Id), command.Address); if (response.HasError && response.Error.ErrorCode != ErrorCode.DuplicateTransactionId) { _log.Error($"{nameof(BitcoinCommandHandler)}:{nameof(Commands.SegwitTransferCommand)}", new Exception(response.ToJson()), context: command.ToJson()); return(CommandHandlingResult.Fail(_retryTimeout)); } ChaosKitty.Meow(); return(CommandHandlingResult.Ok()); }
public async Task Handle(LimitOrderExecutedEvent evt, ICommandSender commandSender) { if (evt.IsTrustedClient) { return; } ChaosKitty.Meow(); var cmd = new UpdateLimitOrdersCountCommand { ClientId = evt.LimitOrder.Order.ClientId, IsTrustedClient = evt.IsTrustedClient }; commandSender.SendCommand(cmd, "operations-history"); }
public async Task <CommandHandlingResult> Handle(RegisterCashInOutCommand command, IEventPublisher eventPublisher) { var id = command.CommandId; var asset = command.Asset; var amount = command.Amount; var transaction = command.Transaction; ChaosKitty.Meow(); try { await _cashOperationsRepositoryClient.RegisterAsync(new CashInOutOperation { Id = id, ClientId = transaction.ClientId, Multisig = transaction.Multisig, AssetId = asset.Id, Amount = amount, BlockChainHash = transaction.Hash, DateTime = DateTime.UtcNow, AddressTo = transaction.Multisig, State = TransactionStates.SettledOnchain }); } catch (HttpOperationException) { var persistedOperation = await _cashOperationsRepositoryClient.GetAsync(transaction.ClientId, id); if (persistedOperation == null) { throw; } // else assuming that operation was correctly persisted before } eventPublisher.PublishEvent(new CashInOutOperationRegisteredEvent { CommandId = command.CommandId, Asset = command.Asset, Amount = command.Amount, Transaction = command.Transaction }); return(CommandHandlingResult.Ok()); }
public async Task <CommandHandlingResult> Handle(RegisterBitcoinCashInCommand command, IEventPublisher eventPublisher) { var id = command.CommandId; var transaction = command.Transaction; ChaosKitty.Meow(); await _bitcoinCashinRepository.InsertOrReplaceAsync(id, transaction.ClientId, transaction.Multisig, transaction.Hash, transaction.IsSegwit); eventPublisher.PublishEvent(new BitcoinCashInRegisteredEvent { CommandId = command.CommandId, Asset = command.Asset, Amount = command.Amount, Transaction = command.Transaction }); return(CommandHandlingResult.Ok()); }
public async Task ProcessMessage(CashInOutQueueMessage message) { _log.Info(message: "Processing message", message.ToJson()); ChaosKitty.Meow(); var transaction = await _transactionsRepository.FindByTransactionIdAsync(message.Id); if (transaction == null) { if (_cashOperationsRepositoryClient.GetAsync(message.ClientId, message.Id) == null) { _log.Warning($"{nameof(CashInOutQueue)}:{nameof(CashInOutQueueMessage)}", "unknown transaction", context: message.ToJson()); return; } await ProcessExternalCashin(message); } else { switch (transaction.CommandType) { case BitCoinCommands.CashIn: case BitCoinCommands.Issue: await ProcessIssue(message); break; case BitCoinCommands.CashOut: await ProcessCashOut(message); break; case BitCoinCommands.ManualUpdate: ProcessManualUpdate(message); break; default: _log.Warning($"{nameof(CashInOutQueue)}:{nameof(CashInOutQueueMessage)}", $"Unknown command type (value = [{transaction.CommandType}])", context: message.ToJson()); break; } } }
public async Task <CommandHandlingResult> Handle(CreateTransactionCommand command) { var sw = new Stopwatch(); sw.Start(); try { await _transactionsRepository.TryCreateAsync(command.OrderId, BitCoinCommands.SwapOffchain, "", null, ""); ChaosKitty.Meow(); return(CommandHandlingResult.Ok()); } finally { sw.Stop(); _log.Info("Command execution time", context: new { TxHandler = new { Handler = nameof(TradeCommandHandler), Command = nameof(CreateTransactionCommand), Time = sw.ElapsedMilliseconds } }); } }
// entry point public async Task <CommandHandlingResult> Handle(ProcessTransactionCommand command, IEventPublisher eventPublisher) { var confirmations = (await _qBitNinjaApiCaller.GetTransaction(command.TransactionHash))?.Block?.Confirmations; var isConfirmed = confirmations >= _settings.TxDetectorConfirmationsLimit; if (!isConfirmed) { //put back if not confirmed yet return(new CommandHandlingResult { Retry = true, RetryDelay = (long)RetryTimeoutForTransactionConfirmations.TotalMilliseconds }); } ChaosKitty.Meow(); var hash = command.TransactionHash; var balanceChangeTransactions = await _balanceChangeTransactionsRepository.GetAsync(hash); foreach (var tx in balanceChangeTransactions) { var alreadyProcessed = !await _confirmedTransactionsRepository.SaveConfirmedIfNotExist(hash, tx.ClientId); if (alreadyProcessed) { _log.WriteInfo(nameof(ProcessTransactionCommand), command, $"Transaction with hash {hash} for client {tx.ClientId} is already processed; ignoring it."); continue; } eventPublisher.PublishEvent(new ConfirmationSavedEvent { TransactionHash = hash, ClientId = tx.ClientId, Multisig = tx.Multisig }); } return(CommandHandlingResult.Ok()); }
//TODO: Split wth the help of the process management private async Task ProcessFailedCashout(ProcessEthCoinEventCommand queueMessage) { CashOutContextData context = await _transactionService.GetTransactionContext <CashOutContextData>(queueMessage.OperationId); if (context != null) { string transactionHandlerUserId = "auto redeem"; string clientId = context.ClientId; string hash = queueMessage.TransactionHash; string cashOperationId = context.CashOperationId; string assetId = context.AssetId; var amount = context.Amount; try { ChaosKitty.Meow(); var asset = await _assetsService.AssetGetAsync(assetId); await _cashOperationsRepositoryClient.UpdateBlockchainHashAsync(clientId, cashOperationId, hash); var pt = await _paymentTransactionsRepository.TryCreateAsync(PaymentTransaction.Create(hash, CashInPaymentSystem.Ethereum, clientId, (double)amount, asset.DisplayId ?? asset.Id, status: PaymentStatus.Processing)); if (pt == null) { _log.Warning($"{nameof(EthereumCoreCommandHandler)}:{nameof(ProcessFailedCashout)}", "Transaction already handled", context: hash); return; //if was handled previously } ChaosKitty.Meow(); var sign = "+"; var commentText = $"Balance Update: {sign}{amount} {asset.Name}. Cashout failed: {hash} {queueMessage.OperationId}"; var newComment = new ClientComment { ClientId = clientId, Comment = commentText, CreatedAt = DateTime.UtcNow, FullName = "Lykke.Job.TransactionHandler", UserId = transactionHandlerUserId }; var exResult = await _exchangeOperationsServiceClient.ExchangeOperations.ManualCashInAsync( new ManualCashInRequestModel { ClientId = clientId, AssetId = assetId, Amount = (double)amount, UserId = "auto redeem", Comment = commentText, }); if (!exResult.IsOk()) { _log.Warning($"{nameof(EthereumCoreCommandHandler)}:{nameof(ProcessFailedCashout)}", "ME operation failed", context: new { ExchangeServiceResponse = exResult, QueueMessage = queueMessage }.ToJson()); } await _clientCommentsRepository.AddClientCommentAsync(newComment); await _paymentTransactionsRepository.SetStatus(hash, PaymentStatus.NotifyProcessed); ChaosKitty.Meow(); } catch (Exception e) { _log.Error($"{nameof(EthereumCoreCommandHandler)}:{nameof(ProcessFailedCashout)}", e, context: queueMessage.ToJson()); throw; } } else { _log.Warning("Can't get a context", context: queueMessage.ToJson()); } }
public async Task <CommandHandlingResult> Handle(EnrollEthCashinToMatchingEngineCommand command, IEventPublisher eventPublisher) { try { var cashinId = command.CashinOperationId.ToString("N"); var clientId = command.ClientId; var hash = command.TransactionHash; var amount = command.Amount; var asset = await _assetsServiceWithCache.TryGetAssetAsync(command.AssetId); var createPendingActions = command.CreatePendingActions; var paymentTransaction = PaymentTransaction.Create(hash, CashInPaymentSystem.Ethereum, clientId.ToString(), (double)amount, asset.DisplayId ?? asset.Id, status: PaymentStatus.Processing); var exists = await _paymentTransactionsRepository.CheckExistsAsync(paymentTransaction); if (exists) { _log.Warning(command.TransactionHash ?? "Empty", $"Transaction already handled {hash}", context: command); return(CommandHandlingResult.Ok()); } if (createPendingActions && asset.IsTrusted) { await _ethererumPendingActionsRepository.CreateAsync(clientId.ToString(), Guid.NewGuid().ToString()); } ChaosKitty.Meow(); MeResponseModel result = null; await ExecuteWithTimeoutHelper.ExecuteWithTimeoutAsync(async() => { result = await _matchingEngineClient.CashInOutAsync(cashinId, clientId.ToString(), asset.Id, (double)amount); }, 5 * 60 * 1000); // 5 min in ms if (result == null || (result.Status != MeStatusCodes.Ok && result.Status != MeStatusCodes.Duplicate)) { _log.Warning(command.TransactionHash ?? "Empty", "ME error", context: result); return(CommandHandlingResult.Fail(TimeSpan.FromMinutes(1))); } eventPublisher.PublishEvent(new EthCashinEnrolledToMatchingEngineEvent() { TransactionHash = hash, }); ChaosKitty.Meow(); await _paymentTransactionsRepository.TryCreateAsync(paymentTransaction); return(CommandHandlingResult.Ok()); } catch (Exception e) { _log.Error(nameof(EnrollEthCashinToMatchingEngineCommand), e, context: command); throw; } }
public async Task <CommandHandlingResult> Handle(TransferEthereumCommand command, IEventPublisher eventPublisher) { var sw = new Stopwatch(); sw.Start(); try { var txRequest = await _ethereumTransactionRequestRepository.GetAsync(command.TransactionId); // todo: udpate txRequest in separated command var context = await _transactionService.GetTransactionContext <TransferContextData>(command.TransactionId.ToString()); txRequest.OperationIds = new[] { context.Transfers[0].OperationId, context.Transfers[1].OperationId }; await _ethereumTransactionRequestRepository.UpdateAsync(txRequest); ChaosKitty.Meow(); var clientAddress = await _bcnClientCredentialsRepository.GetClientAddress(txRequest.ClientId); var hotWalletAddress = _settings.HotwalletAddress; string addressFrom; string addressTo; Guid transferId; string sign; switch (txRequest.OperationType) { case OperationType.TransferToTrusted: addressFrom = clientAddress; addressTo = hotWalletAddress; transferId = txRequest.SignedTransfer.Id; sign = txRequest.SignedTransfer.Sign; break; case OperationType.TransferFromTrusted: addressFrom = hotWalletAddress; addressTo = clientAddress; transferId = txRequest.Id; sign = string.Empty; break; case OperationType.TransferBetweenTrusted: return(CommandHandlingResult.Ok()); default: _log.Error(nameof(TransferEthereumCommand), message: "Unknown transfer type"); return(CommandHandlingResult.Fail(_retryTimeout)); } var asset = await _assetsServiceWithCache.TryGetAssetAsync(txRequest.AssetId); var response = await _srvEthereumHelper.SendTransferAsync(transferId, sign, asset, addressFrom, addressTo, txRequest.Volume); ChaosKitty.Meow(); if (response.HasError && response.Error.ErrorCode != ErrorCode.OperationWithIdAlreadyExists && response.Error.ErrorCode != ErrorCode.EntityAlreadyExists) { var errorMessage = response.Error.ToJson(); _log.Error(nameof(TransferEthereumCommand), new Exception(errorMessage)); return(CommandHandlingResult.Fail(_retryTimeout)); } eventPublisher.PublishEvent(new EthereumTransferSentEvent { TransferId = transferId }); ChaosKitty.Meow(); return(CommandHandlingResult.Ok()); } finally { sw.Stop(); _log.Info("Command execution time", context: new { TxHandler = new { Handler = nameof(EthereumCommandHandler), Command = nameof(TransferEthereumCommand), Time = sw.ElapsedMilliseconds } }); } }