Пример #1
0
        public void ProcessOrders()
        {
            // if tripwire tripped cancel
            if (!_tripwire.TradingEnabled() || !_tripwire.WithdrawalsEnabled())
            {
                _logger.LogError("Tripwire tripped, exiting ProcessOrders()");
                return;
            }
            // get lock - ensure that this function ends before it is started again
            lock (lockObj)
            {
                _logger.LogInformation("Process Orders - Broker");
                var date = DateTimeOffset.Now.ToUnixTimeSeconds();
                // process created orders
                var orders = _context.BrokerOrders.Where(o => o.Status == BrokerOrderStatus.Ready.ToString() || o.Status == BrokerOrderStatus.Incomming.ToString() ||
                                                         o.Status == BrokerOrderStatus.Confirmed.ToString() || o.Status == BrokerOrderStatus.PayoutWait.ToString()).ToList();
                foreach (var order in orders)
                {
                    if (_walletProvider.IsChain(order.AssetSend))
                    {
                        ProcessOrderChain(order);
                    }
                    else
                    {
                        ProcessOrderFiat(order);
                    }

                    // we should save changes here because calls to the exchange backend could potentially throw exceptions
                    // which this would leave the backend and frontend in conflicting state (if we save after the loop finishes)
                    // I just eagerly load 'orders' using ToList() at the end of the linq statement to make sure that doesnt get invalidated or anything
                    _context.SaveChanges();
                }
                // expire orders
                var ordersToExpire = _context.BrokerOrders.Where(o => o.Status == BrokerOrderStatus.Created.ToString() || o.Status == BrokerOrderStatus.Ready.ToString());
                foreach (var order in ordersToExpire)
                {
                    if (date > order.Expiry + _apiSettings.Broker.TimeLimitGracePeriod)
                    {
                        order.Status = BrokerOrderStatus.Expired.ToString();
                        _context.BrokerOrders.Update(order);
                    }
                }
                _context.SaveChanges();
            }
        }
Пример #2
0
        public async Task <IActionResult> Withdraw(WithdrawViewModel model)
        {
            var user = await GetUser(required : true);

            //TODO: move this to a ViaRpcProvider in /Services (like IWalletProvider)
            var via     = new ViaJsonRpc(_settings.AccessHttpUrl);
            var balance = via.BalanceQuery(user.Exchange.Id, model.Asset);

            // fill in model in case we need to error out early
            model.User             = user;
            model.AssetSettings    = _settings.Assets;
            model.BalanceAvailable = balance.Available;

            // if tripwire tripped cancel
            if (!_tripwire.WithdrawalsEnabled())
            {
                _logger.LogError("Tripwire tripped, exiting Withdraw()");
                this.FlashError($"Withdrawals not enabled");
                return(View(model));
            }
            await _tripwire.RegisterEvent(TripwireEventType.WithdrawalAttempt);

            if (!ModelState.IsValid)
            {
                // redisplay form
                return(View(model));
            }

            // check 2fa authentication
            if (user.TwoFactorEnabled)
            {
                if (model.TwoFactorCode == null)
                {
                    model.TwoFactorCode = "";
                }
                var authenticatorCode = model.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
                if (!await _userManager.VerifyTwoFactorTokenAsync(user, _userManager.Options.Tokens.AuthenticatorTokenProvider, authenticatorCode))
                {
                    this.FlashError($"Invalid authenticator code");
                    return(View(model));
                }
            }

            // lock process of checking balance and performing withdrawal
            lock (_userLocks.GetLock(user.Id))
            {
                //TODO: move this to a ViaRpcProvider in /Services (like IWalletProvider)
                balance = via.BalanceQuery(user.Exchange.Id, model.Asset);
                model.BalanceAvailable = balance.Available;

                var wallet = _walletProvider.GetChain(model.Asset);

                // validate amount
                var amountInt    = wallet.StringToAmount(model.Amount.ToString());
                var availableInt = wallet.StringToAmount(balance.Available);
                if (amountInt > availableInt)
                {
                    this.FlashError("Amount must be less then or equal to available balance");
                    return(View(model));
                }
                if (amountInt <= 0)
                {
                    this.FlashError("Amount must be greather then or equal to 0");
                    return(View(model));
                }

                // validate address
                if (!wallet.ValidateAddress(model.WithdrawalAddress))
                {
                    this.FlashError("Withdrawal address is not valid");
                    return(View(model));
                }

                // validate kyc level
                (var success, var withdrawalAssetAmount, var error) = ValidateWithdrawlLimit(user, model.Asset, model.Amount);
                if (!success)
                {
                    this.FlashError(error);
                    return(View(model));
                }

                var consolidatedFundsTag = _walletProvider.ConsolidatedFundsTag();

                using (var dbtx = wallet.BeginDbTransaction())
                {
                    // ensure tag exists
                    if (!wallet.HasTag(consolidatedFundsTag))
                    {
                        wallet.NewTag(consolidatedFundsTag);
                        wallet.Save();
                    }

                    // register withdrawal with wallet
                    var tag = wallet.GetTag(user.Id);
                    if (tag == null)
                    {
                        tag = wallet.NewTag(user.Id);
                    }
                    var spend = wallet.RegisterPendingSpend(consolidatedFundsTag, consolidatedFundsTag,
                                                            model.WithdrawalAddress, amountInt, tag);
                    wallet.Save();
                    var businessId = spend.Id;

                    // register withdrawal with the exchange backend
                    var negativeAmount = -model.Amount;
                    try
                    {
                        via.BalanceUpdateQuery(user.Exchange.Id, model.Asset, "withdraw", businessId, negativeAmount.ToString(), null);
                    }
                    catch (ViaJsonException ex)
                    {
                        _logger.LogError(ex, "Failed to update (withdraw) user balance (xch id: {0}, asset: {1}, businessId: {2}, amount {3}",
                                         user.Exchange.Id, model.Asset, businessId, negativeAmount);
                        if (ex.Err == ViaError.BALANCE_UPDATE__BALANCE_NOT_ENOUGH)
                        {
                            dbtx.Rollback();
                            this.FlashError("Balance not enough");
                            return(View(model));
                        }
                        throw;
                    }

                    dbtx.Commit();
                }

                // register withdrawal with kyc limits
                user.AddWithdrawal(_context, model.Asset, model.Amount, withdrawalAssetAmount);
                _context.SaveChanges();
            }

            // register withdrawal with tripwire
            await _tripwire.RegisterEvent(TripwireEventType.Withdrawal);

            this.FlashSuccess(string.Format("Created withdrawal: {0} {1} to {2}", model.Amount, model.Asset, model.WithdrawalAddress));
            // send email: withdrawal created
            await _emailSender.SendEmailChainWithdrawalCreatedAsync(user.Email, model.Asset, model.Amount.ToString());

            return(View(model));
        }
Пример #3
0
        public void ProcessChainWithdrawals()
        {
            if (!_tripwire.WithdrawalsEnabled())
            {
                _logger.LogError("Tripwire tripped, exiting ProcessChainWithdrawls()");
                return;
            }
            lock (lockObj)
            {
                // if lockfile exists exit early
                var lockFile = new LockFile(_logger, "chain_withdrawals");
                if (lockFile.IsPresent())
                {
                    _logger.LogError($"lockfile ('{lockFile.MkPath()}) exists");
                    return;
                }

                using (var scope = _services.CreateScope())
                {
                    var settings       = scope.ServiceProvider.GetRequiredService <IOptions <WalletSettings> >().Value;
                    var walletProvider = scope.ServiceProvider.GetRequiredService <IWalletProvider>();
                    var userManager    = scope.ServiceProvider.GetRequiredService <UserManager <ApplicationUser> >();
                    var emailSender    = scope.ServiceProvider.GetRequiredService <IEmailSender>();

                    foreach (var asset in settings.ChainAssetSettings.Keys)
                    {
                        // get wallet
                        var wallet        = walletProvider.GetChain(asset);
                        var assetSettings = walletProvider.ChainAssetSettings(asset);
                        // get pending spends
                        var spends = wallet.PendingSpendsGet(null, new PendingSpendState[] { PendingSpendState.Pending, PendingSpendState.Error }).ToList();
                        foreach (var spend in spends)
                        {
                            // recheck tripwire
                            if (!_tripwire.WithdrawalsEnabled())
                            {
                                _logger.LogError("Tripwire tripped, exiting ProcessChainWithdrawls()");
                                return;
                            }

                            // check and create lockfile to make sure we cant send withdrawals twice
                            if (lockFile.IsPresent())
                            {
                                _logger.LogError($"lockfile ('{lockFile.MkPath()}) exists");
                                return;
                            }
                            var contents = $"Pending spend: {spend.SpendCode}, {spend.State}, {spend.Date}, {spend.Amount} {asset} cents";
                            if (!lockFile.CreateIfNotPresent(contents))
                            {
                                _logger.LogError($"failed to create lockfile ('{lockFile.MkPath()})");
                                return;
                            }

                            _logger.LogInformation($"SpendCode: {spend.SpendCode}, Date: {spend.Date}, Amount: {spend.Amount}, To: {spend.To}, State: {spend.State}");
                            // process withdrawal
                            _logger.LogInformation($"Actioning pending spend: {spend.SpendCode}, asset: {asset}");
                            var err = wallet.PendingSpendAction(spend.SpendCode, assetSettings.FeeMax, assetSettings.FeeUnit, out IEnumerable <WalletTx> wtxs);
                            _logger.LogInformation($"Result: {err}");

                            // save wallet
                            wallet.Save();
                            _logger.LogInformation($"Saved {asset} wallet");

                            // remove lock file now that we have saved wallet status
                            if (!lockFile.RemoveIfPresent())
                            {
                                _logger.LogError($"Failed to remove lockfile ({lockFile.MkPath()})");
                            }

                            if (err == WalletError.Success)
                            {
                                foreach (var wtx in wtxs)
                                {
                                    // get user
                                    System.Diagnostics.Debug.Assert(spend.TagFor != null);
                                    var user = userManager.FindByIdAsync(spend.TagFor.Tag).GetAwaiter().GetResult();
                                    System.Diagnostics.Debug.Assert(user != null);

                                    // send email
                                    emailSender.SendEmailChainWithdrawalConfirmedAsync(user.Email, asset, wallet.AmountToString(wtx.AmountInputs() - wtx.ChainTx.Fee), wtx.ChainTx.TxId).GetAwaiter().GetResult();
                                    _logger.LogInformation($"Sent email to {user.Email}");
                                }
                            }
                        }
                    }
                }
            }
        }