public async Task <CustomerProfileErrorCodes> RequestCustomerProfileDeactivation(string customerId)
        {
            return(await _transactionRunner.RunWithTransactionAsync(async txContext =>
            {
                var customerProfile =
                    await _customerProfileRepository.GetByCustomerIdAsync(customerId, includeNotVerified: true, includeNotActive: true, txContext);

                if (customerProfile == null)
                {
                    return CustomerProfileErrorCodes.CustomerProfileDoesNotExist;
                }

                if (customerProfile.Status != CustomerProfileStatus.Active)
                {
                    return CustomerProfileErrorCodes.CustomerIsNotActive;
                }

                await _customerProfileRepository.ChangeProfileStatus(customerId, CustomerProfileStatus.PendingDeactivation, txContext);

                await _deactivationRequestedPublisher.PublishAsync(new CustomerProfileDeactivationRequestedEvent
                {
                    CustomerId = customerId
                });

                return CustomerProfileErrorCodes.None;
            }));
        }
        private async Task <string> BuildTransactionAsync(
            string operationId,
            string masterWalletAddress,
            string targetWalletAddress,
            string encodedData)
        {
            return(await _transactionRunner.RunWithTransactionAsync(async txContext =>
            {
                var nonce = await _noncesRepository.GetNextNonceAsync(masterWalletAddress, txContext);

                var transaction = new Transaction(
                    to: targetWalletAddress,
                    amount: 0,
                    nonce: nonce,
                    gasPrice: _gasPrice,
                    gasLimit: _gasLimit,
                    data: encodedData
                    );

                _log.Info
                (
                    "Transaction has been built.",
                    new { operationId, masterWalletAddress, nonce }
                );

                try
                {
                    var(v, r, s) = await _transactionSigner.SignTransactionAsync(masterWalletAddress, transaction.RawHash);

                    var signature = EthECDSASignatureFactory.FromComponents(r: r, s: s, v: v);

                    transaction.SetSignature(signature);

                    #region Logging

                    _log.Info
                    (
                        "Transaction has been signed.",
                        new { operationId, masterWalletAddress, nonce }
                    );

                    #endregion
                }
                catch (Exception e)
                {
                    #region Logging

                    _log.Error
                    (
                        e,
                        "Failed to sign transaction.",
                        new { operationId, masterWalletAddress, nonce }
                    );

                    #endregion

                    throw;
                }

                var transactionData = transaction.GetRLPEncoded().ToHex(true);

                try
                {
                    await _operationsRepository.SetTransactionDataAsync(
                        operationId,
                        transactionData,
                        txContext);

                    #region Logging

                    _log.Info
                    (
                        "Transaction data has been saved.",
                        new { operationId, masterWalletAddress, nonce }
                    );

                    #endregion
                }
                catch (Exception e)
                {
                    #region Logging

                    _log.Error
                    (
                        e,
                        "Failed to save transaction data.",
                        new { operationId, masterWalletAddress, nonce }
                    );

                    #endregion

                    throw;
                }

                return transactionData;
            }));
        }
Beispiel #3
0
        public async Task <ReferralStakesStatusUpdateErrorCode> TokensBurnAndReleaseAsync(string referralId, decimal?releaseBurnRatio = null)
        {
            if (string.IsNullOrEmpty(referralId))
            {
                throw new ArgumentNullException(nameof(referralId));
            }

            var newStatus = StakeStatus.TokensBurnAndReleaseStarted;

            var transactionError = await _transactionRunner.RunWithTransactionAsync(async txContext =>
            {
                var referralStake = await _referralStakesRepository.GetByReferralIdAsync(referralId, txContext);

                if (referralStake == null)
                {
                    return(ReferralStakesStatusUpdateErrorCode.DoesNotExist);
                }

                if (referralStake.Status != StakeStatus.TokensReservationSucceeded)
                {
                    return(ReferralStakesStatusUpdateErrorCode.InvalidStatus);
                }

                var customerWalletAddress =
                    await _pbfClient.CustomersApi.GetWalletAddress(Guid.Parse(referralStake.CustomerId));

                if (customerWalletAddress.Error == CustomerWalletAddressError.CustomerWalletMissing)
                {
                    return(ReferralStakesStatusUpdateErrorCode.CustomerWalletIsMissing);
                }

                var walletBlockStatus =
                    await _walletManagementClient.Api.GetCustomerWalletBlockStateAsync(referralStake.CustomerId);

                if (walletBlockStatus.Status == CustomerWalletActivityStatus.Blocked)
                {
                    return(ReferralStakesStatusUpdateErrorCode.CustomerWalletBlocked);
                }

                referralStake.Status           = newStatus;
                referralStake.ReleaseBurnRatio = releaseBurnRatio;

                var burnRatio = releaseBurnRatio ?? referralStake.ExpirationBurnRatio;

                var(amountToBurn, amountToRelease) =
                    CalculateAmountToBurnAndRelease(referralStake.Amount, burnRatio);

                var encodedData =
                    _blockchainEncodingService.EncodeDecreaseRequestData(customerWalletAddress.WalletAddress,
                                                                         amountToBurn, amountToRelease);

                var operationId = await AddBlockchainOperation(encodedData);

                await _stakesBlockchainDataRepository.UpsertAsync(referralId, operationId, txContext);

                await _referralStakesRepository.UpdateReferralStakeAsync(referralStake, txContext);

                return(ReferralStakesStatusUpdateErrorCode.None);
            });

            if (transactionError == ReferralStakesStatusUpdateErrorCode.None)
            {
                await PublishStatusUpdatedEvent(referralId, newStatus);
            }

            return(transactionError);
        }
Beispiel #4
0
        public async Task <ReferralStakeRequestErrorCode> ReferralStakeAsync(ReferralStakeModel model)
        {
            #region Validation

            if (string.IsNullOrEmpty(model.CustomerId))
            {
                throw new ArgumentNullException(nameof(model.CustomerId));
            }

            if (string.IsNullOrEmpty(model.CampaignId))
            {
                throw new ArgumentNullException(nameof(model.CampaignId));
            }

            if (string.IsNullOrEmpty(model.ReferralId))
            {
                throw new ArgumentNullException(nameof(model.ReferralId));
            }

            if (model.Amount <= 0)
            {
                return(ReferralStakeRequestErrorCode.InvalidAmount);
            }

            if (model.WarningPeriodInDays < 0)
            {
                return(ReferralStakeRequestErrorCode.InvalidWarningPeriodInDays);
            }

            if (model.StakingPeriodInDays <= 0)
            {
                return(ReferralStakeRequestErrorCode.InvalidStakingPeriodInDays);
            }

            if (model.StakingPeriodInDays <= model.WarningPeriodInDays)
            {
                return(ReferralStakeRequestErrorCode.WarningPeriodShouldSmallerThanStakingPeriod);
            }

            if (model.ExpirationBurnRatio < 0 || model.ExpirationBurnRatio > 100)
            {
                return(ReferralStakeRequestErrorCode.InvalidBurnRatio);
            }

            var existingStake = await _referralStakesRepository.GetByReferralIdAsync(model.ReferralId);

            if (existingStake != null)
            {
                return(ReferralStakeRequestErrorCode.StakeAlreadyExist);
            }

            var customerProfile = await _customerProfileClient.CustomerProfiles.GetByCustomerIdAsync(model.CustomerId);

            if (customerProfile.ErrorCode == CustomerProfileErrorCodes.CustomerProfileDoesNotExist)
            {
                return(ReferralStakeRequestErrorCode.CustomerDoesNotExist);
            }

            var campaign = await _campaignClient.Campaigns.GetByIdAsync(model.CampaignId);

            if (campaign.ErrorCode == CampaignServiceErrorCodes.EntityNotFound)
            {
                return(ReferralStakeRequestErrorCode.CampaignDoesNotExist);
            }

            var walletBlockStatus =
                await _walletManagementClient.Api.GetCustomerWalletBlockStateAsync(model.CustomerId);

            if (walletBlockStatus.Status == CustomerWalletActivityStatus.Blocked)
            {
                return(ReferralStakeRequestErrorCode.CustomerWalletBlocked);
            }

            var isCustomerIdValidGuid = Guid.TryParse(model.CustomerId, out var customerIdGuid);

            if (!isCustomerIdValidGuid)
            {
                return(ReferralStakeRequestErrorCode.InvalidCustomerId);
            }

            var customerWalletAddress = await _pbfClient.CustomersApi.GetWalletAddress(customerIdGuid);

            if (customerWalletAddress.Error == CustomerWalletAddressError.CustomerWalletMissing)
            {
                return(ReferralStakeRequestErrorCode.CustomerWalletIsMissing);
            }

            var balance = await _pbfClient.CustomersApi.GetBalanceAsync(customerIdGuid);

            if (balance.Total < model.Amount)
            {
                return(ReferralStakeRequestErrorCode.NotEnoughBalance);
            }

            #endregion

            model.Status    = StakeStatus.TokensReservationStarted;
            model.Timestamp = DateTime.UtcNow;

            var encodedData =
                _blockchainEncodingService.EncodeStakeRequestData(customerWalletAddress.WalletAddress, model.Amount);

            var blockchainOperationId = await _pbfClient.OperationsApi.AddGenericOperationAsync(new GenericOperationRequest
            {
                Data          = encodedData,
                SourceAddress = _settingsService.GetMasterWalletAddress(),
                TargetAddress = _settingsService.GetTokenContractAddress()
            });

            return(await _transactionRunner.RunWithTransactionAsync(async txContext =>
            {
                await _referralStakesRepository.AddAsync(model, txContext);
                await _stakesBlockchainDataRepository.UpsertAsync(model.ReferralId, blockchainOperationId.OperationId.ToString(),
                                                                  txContext);

                return ReferralStakeRequestErrorCode.None;
            }));
        }
        public async Task <bool> DecodeNonDecodedTopicsAsync(
            int batchSize)
        {
            using (_log.BeginScope($"{nameof(DecodeNonDecodedTopicsAsync)}-{Guid.NewGuid()}"))
            {
                IReadOnlyCollection <TransactionLog> nonDecodedTransactionLogs;

                try
                {
                    #region Logging

                    _log.Debug($"Getting up to {batchSize} non-decoded transaction logs  with known ABIs.");

                    #endregion

                    nonDecodedTransactionLogs = (await _transactionRepository.GetNonDecodedTransactionLogsAsync(batchSize)).ToList();

                    #region Logging

                    _log.Debug($"Got {nonDecodedTransactionLogs.Count} non-decoded transaction logs with known ABIs.");

                    #endregion
                }
                catch (Exception e)
                {
                    #region Logging

                    _log.Error(e, $"Failed to get up to {batchSize} non-decoded transaction logs with known ABIs.");

                    #endregion

                    return(false);
                }

                if (nonDecodedTransactionLogs.Count == 0)
                {
                    #region Logging

                    _log.Debug("No non-decoded transaction logs with known ABIs have been found.");

                    #endregion

                    return(false);
                }

                List <Event> decodedEvents;

                try
                {
                    #region Logging

                    _log.Debug($"Decoding {nonDecodedTransactionLogs.Count} transaction logs.");

                    #endregion

                    decodedEvents = nonDecodedTransactionLogs.Select(x => _decodingService.DecodeTransactionLog(x)).ToList();

                    #region Logging

                    _log.Debug($"{nonDecodedTransactionLogs.Count} transaction logs have been decoded.");

                    #endregion
                }
                catch (Exception e)
                {
                    #region Logging

                    _log.Error(e, "Failed to decode transaction logs.");

                    #endregion

                    return(false);
                }

                try
                {
                    await _transactionRunner.RunWithTransactionAsync(async (txContext) =>
                    {
                        await _eventRepository.SaveAsync(decodedEvents, txContext);

                        await _transactionRepository.SaveDecodedAsync(
                            nonDecodedTransactionLogs.Select(t => Tuple.Create(t.LogIndex, t.TransactionHash)));
                    });
                }
                catch (InvalidOperationException e)
                {
                    #region Logging

                    _log.Error(e, "Failed to commit transaction.");

                    #endregion

                    return(false);
                }
                catch (Exception e)
                {
                    #region Logging

                    _log.Error(e, "Failed to save decoded transaction logs.");

                    #endregion

                    return(false);
                }

                #region Logging

                _log.Info($"{nonDecodedTransactionLogs.Count} transaction logs have been decoded.");

                #endregion

                return(true);
            }
        }
        public async Task <LinkingApprovalResultModel> ApproveLinkRequestAsync(string privateAddress, string publicAddress, string signature)
        {
            #region Validation

            if (string.IsNullOrEmpty(privateAddress))
            {
                return(LinkingApprovalResultModel.Failed(LinkingError.InvalidPrivateAddress));
            }

            if (string.IsNullOrEmpty(publicAddress))
            {
                return(LinkingApprovalResultModel.Failed(LinkingError.InvalidPublicAddress));
            }

            if (string.IsNullOrEmpty(signature))
            {
                return(LinkingApprovalResultModel.Failed(LinkingError.InvalidSignature));
            }

            var linkRequest = await _requestsRepository.GetByPrivateAddressAsync(privateAddress);

            if (linkRequest == null)
            {
                return(LinkingApprovalResultModel.Failed(LinkingError.LinkingRequestDoesNotExist));
            }

            if (linkRequest.IsApproved())
            {
                return(LinkingApprovalResultModel.Failed(LinkingError.LinkingRequestAlreadyApproved));
            }

            var walletError = await CheckWalletStatus(linkRequest.CustomerId);

            if (walletError != LinkingError.None)
            {
                return(LinkingApprovalResultModel.Failed(walletError));
            }

            if (!_signatureValidator.Validate(linkRequest.LinkCode, signature, publicAddress))
            {
                return(LinkingApprovalResultModel.Failed(LinkingError.InvalidSignature));
            }

            var fee = await _customersService.GetNextWalletLinkingFeeAsync(Guid.Parse(linkRequest.CustomerId));

            if (fee > 0)
            {
                var balanceResult = await _pbfClient.CustomersApi.GetBalanceAsync(Guid.Parse(linkRequest.CustomerId));

                if (balanceResult.Error != CustomerBalanceError.None)
                {
                    _log.Error(message: "Couldn't get balance for customer wallet",
                               context: new { customerId = linkRequest.CustomerId, error = balanceResult.Error.ToString() });

                    return(LinkingApprovalResultModel.Failed(LinkingError.NotEnoughFunds));
                }

                if (balanceResult.Total < fee)
                {
                    _log.Warning("The balance is not enough to pay wallet linking fee",
                                 context: new { balanceTotal = balanceResult.Total, fee });

                    return(LinkingApprovalResultModel.Failed(LinkingError.NotEnoughFunds));
                }
            }

            #endregion

            await _transactionRunner.RunWithTransactionAsync(async txContext =>
            {
                await _requestsRepository.SetApprovedAsync(linkRequest.CustomerId, publicAddress, signature, fee,
                                                           txContext);

                var counter = await _countersRepository.GetAsync(linkRequest.CustomerId, txContext);

                var newCounterValue = counter?.ApprovalsCounter + 1 ?? 1;

                await _countersRepository.UpsertAsync(linkRequest.CustomerId, newCounterValue, txContext);
            });

            await PublishLinkCommandAsync(linkRequest.CustomerId, privateAddress, publicAddress, fee);

            _log.Info("Wallet linking request has been approved", new { linkRequest.CustomerId, publicAddress, fee });

            return(LinkingApprovalResultModel.Succeeded());
        }