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(); } }
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)); }
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}"); } } } } } } }