private async Task UpdateWithTransactionDetails(SignalCircleTransfer transfer) { using var activity = MyTelemetry.StartActivity("Handle event SignalCircleTransfer"); transfer.AddToActivityAsTag("circle-transfer"); try { await using var context = new DatabaseContext(_dbContextOptionsBuilder.Options); var withdrawal = await context.Withdrawals .Where(e => e.Integration == "Circle" && e.ExternalSystemId == transfer.PaymentInfo.Id) .FirstOrDefaultAsync(); if (withdrawal == null) { _logger.LogError("Unable to find withdrawal with external system id {withdrawalId}", transfer.PaymentInfo.Id); return; } withdrawal.Txid = transfer.PaymentInfo.TransactionHash; await context.UpdateAsync(withdrawal); } catch (Exception) { _logger.LogError("Unable to update withdrawal details {withdrawalId}", transfer.PaymentInfo.Id); } }
private async ValueTask HandleSignal(SignalCircleTransfer signal) { using var activity = MyTelemetry.StartActivity("Handle Event SignalBitGoTransfer"); try { await _service.HandledDepositAsync(signal); } catch (Exception ex) { ex.FailActivity(); throw; } }
private async ValueTask HandleSignal(SignalCircleTransfer signal) { using var activity = MyTelemetry.StartActivity("Handle event SignalCircleTransfer"); signal.AddToActivityAsJsonTag("circle-signal"); _logger.LogInformation("SignalCircleTransfer is received: {jsonText}", JsonConvert.SerializeObject(signal)); if (signal.PaymentInfo == null) { _logger.LogError("Cannot handle Circle signal, transfer do not found {jsonText}", JsonConvert.SerializeObject(signal)); Activity.Current?.SetStatus(Status.Error); return; } if (signal.PaymentInfo.Source.Type != "wallet") { _logger.LogInformation("Skip non-withdrawal transfer {type}", signal.PaymentInfo.Source.Type); return; } if (signal.PaymentInfo.Status == PaymentStatus.Pending) { await UpdateWithTransactionDetails(signal); return; } if (signal.PaymentInfo.Status != PaymentStatus.Complete) { _logger.LogInformation("SignalCircleTransfer is not completed, skipping"); return; } await HandleTransactionFee(signal); _logger.LogInformation("SignalCircleTransfer is handled"); }
public async Task HandledDepositAsync(SignalCircleTransfer transfer) { transfer.AddToActivityAsJsonTag("circle-transfer"); _logger.LogInformation("Request to handle transfer from Circle: {transferJson}", JsonConvert.SerializeObject(transfer)); if (transfer.PaymentInfo.Source.Type != "blockchain" && transfer.PaymentInfo.Source.Type != "card") { _logger.LogInformation("Skip withdrawal transfer {type}", transfer.PaymentInfo.Source.Type); return; } transfer.BrokerId.AddToActivityAsTag("brokerId"); transfer.WalletId.AddToActivityAsTag("walletId"); transfer.ClientId.AddToActivityAsTag("clientId"); var circleAsset = _circleAssetMapper.CircleAssetToAsset(transfer.BrokerId, transfer.PaymentInfo.Amount.Currency); string asset = null; if (transfer.PaymentInfo.Source.Type == "card") { asset = circleAsset?.AssetSymbol; } else if (transfer.PaymentInfo.Source.Type == "blockchain") { asset = circleAsset?.AssetTokenSymbol; } if (string.IsNullOrEmpty(asset)) { _logger.LogError("Unknown circle asset {asset}", transfer.PaymentInfo.Amount.Currency); return; } var accuracy = _assetsDictionary.GetAssetById(new AssetIdentity { Symbol = asset, BrokerId = transfer.BrokerId }).Accuracy; var amount = (decimal)double.Parse(transfer.PaymentInfo.Amount.Amount); var feeAmount = (decimal)double.Parse(string.IsNullOrEmpty(transfer.PaymentInfo?.Fees?.Amount) ? "0.0" : transfer.PaymentInfo.Fees.Amount); var roundedAmount = Math.Round(amount - feeAmount, accuracy, MidpointRounding.ToNegativeInfinity); var isBlockchain = transfer.PaymentInfo.Source.Type == "blockchain"; var circleBlockchain = _circleBlockchainMapper.BlockchainToCircleBlockchain(transfer.BrokerId, transfer.PaymentInfo.Source.Chain); try { await using var ctx = DatabaseContext.Create(_dbContextOptionsBuilder); var existingEntity = await ctx.Deposits.Where(e => e.TransactionId == transfer.PaymentInfo.Id).FirstOrDefaultAsync(); if (existingEntity == null) { _logger.LogInformation("Got unknown deposit from circle, adding entry"); existingEntity = new DepositEntity { BrokerId = transfer.BrokerId, WalletId = transfer.WalletId, ClientId = transfer.ClientId, TransactionId = transfer.PaymentInfo.Id, Amount = (decimal)roundedAmount, AssetSymbol = asset, Comment = isBlockchain ? $"Circle blockchain {transfer.PaymentInfo.Source.Chain} deposit [{asset}]" : $"Circle card deposit [{asset}:{transfer.WalletId}, card: {transfer.PaymentInfo.Source.Id}]", Integration = isBlockchain ? "CircleBlockchain" : "CircleCard", Txid = isBlockchain ? transfer.PaymentInfo.TransactionHash : transfer.PaymentInfo.Id, Status = ConvertExternalStatus(transfer.PaymentInfo.Status), EventDate = DateTime.UtcNow, UpdateDate = DateTime.UtcNow, FeeAmount = feeAmount, FeeAssetSymbol = string.IsNullOrEmpty(transfer.PaymentInfo?.Fees?.Currency) ? transfer.PaymentInfo.Amount.Currency : transfer.PaymentInfo.Fees.Currency, Network = circleBlockchain != null ? circleBlockchain.Blockchain : transfer.PaymentInfo.Source.Chain, MatchingEngineId = "Deposit:" + Guid.NewGuid(), }; } else { existingEntity.Amount = roundedAmount; existingEntity.UpdateDate = DateTime.UtcNow; existingEntity.FeeAmount = feeAmount; existingEntity.FeeAssetSymbol = string.IsNullOrEmpty(transfer.PaymentInfo?.Fees?.Currency) ? transfer.PaymentInfo.Amount.Currency : transfer.PaymentInfo.Fees.Currency; existingEntity.Status = ConvertExternalStatus(transfer.PaymentInfo.Status); } await ctx.UpsertAsync(existingEntity); await _depositPublisher.PublishAsync(new Deposit(existingEntity)); } catch (Exception) { _logger.LogError("Unable to update deposit entry"); throw; } _logger.LogInformation("Deposit request from Circle {transferIdString} is handled", transfer.PaymentInfo.Id); }
private async Task HandleTransactionFee(SignalCircleTransfer transfer) { using var activity = MyTelemetry.StartActivity("Handle event SignalCircleTransfer"); transfer.AddToActivityAsTag("circle-transfer"); try { await using var context = new DatabaseContext(_dbContextOptionsBuilder.Options); var withdrawal = await context.Withdrawals .Where(e => e.Integration == "Circle" && e.ExternalSystemId == transfer.PaymentInfo.Id) .FirstOrDefaultAsync(); if (withdrawal == null) { _logger.LogError("Unable to find withdrawal with external system id {withdrawalId}", transfer.PaymentInfo.Id); return; } withdrawal.Status = Domain.Models.WithdrawalStatus.Success; withdrawal.Txid = transfer.PaymentInfo.TransactionHash; if (!string.IsNullOrEmpty(transfer.PaymentInfo?.Fees?.Currency)) { var asset = _circleAssetMapper.CircleAssetToAsset(withdrawal.BrokerId, transfer.PaymentInfo.Fees.Currency); if (asset == null) { _logger.LogError("Unknown fees asset: {asset}", transfer.PaymentInfo.Fees.Currency); await context.UpdateAsync(withdrawal); return; } var feeStr = transfer.PaymentInfo.Fees.Amount; if (!decimal.TryParse(feeStr, out var fee)) { _logger.LogError( "Cannot read fee from circle transaction. FeeString {feeString}, coin: {coin}, id: {id}", feeStr, asset.AssetTokenSymbol, transfer.PaymentInfo.Id); activity?.SetStatus(Status.Error); await context.UpdateAsync(withdrawal); return; } withdrawal.WalletId.AddToActivityAsTag("walletId"); var request = new BlockchainFeeApplyGrpcRequest() { WalletId = withdrawal.WalletId, BrokerId = withdrawal.BrokerId, TransactionId = transfer.PaymentInfo.TransactionHash, AssetSymbol = asset.AssetSymbol, FeeAmount = fee }; request.AddToActivityAsJsonTag("fee-apply-request"); var result = await _changeBalanceService.BlockchainFeeApplyAsync(request); if (result.ErrorCode != ChangeBalanceGrpcResponse.ErrorCodeEnum.Ok || !result.Result) { _logger.LogError("Cannot apply fee. Request: {requestText}. Error: {jsonText}", JsonConvert.SerializeObject(request), JsonConvert.SerializeObject(result)); activity?.SetStatus(Status.Error); } else { _logger.LogInformation("Success apply fee. Request: {requestText}", JsonConvert.SerializeObject(request)); } withdrawal.ActualFee = fee; withdrawal.ActualFeeAssetSymbol = asset.AssetTokenSymbol; } await context.UpdateAsync(withdrawal); } catch (Exception) { _logger.LogError("Unable to record actual tx fee for withdrawal with id {withdrawalId}", transfer.PaymentInfo.Id); } }