Example #1
0
        public async Task Process(Swap swap, CancellationToken cancellationToken)
        {
            // Ignore transactions that are beyond their deadline, or outside of the configured range.
            if (swap.Deadline < DateTimeOffset.UtcNow.ToUnixTimeSeconds())
            {
                return;
            }

            await _backoffService.Throttle(async() =>
            {
                using var waiter = _transactionWaitService.GetWaiter(swap.TransactionHash);
                var prepWeb3     = new Web3(new Account(_batBotOptions.PrepPrivateKey), _batBotOptions.BlockchainEndpointHttpUrl);

                var detectedMessage = $"๐Ÿ”Ž Detected transaction: {_messagingService.GetEtherscanUrl($"tx/{swap.TransactionHash}")}";
                await SendPreFrontRunMessage(detectedMessage, MessageTier.Double);

                var pair = await _pairInfoService.GetPair(prepWeb3, (swap.Path.First(), swap.Path.Last()), cancellationToken);

                if (pair is null)
                {
                    await SendPreFrontRunMessage("โ›” Unable to obtain pair contract for this swap", MessageTier.End);
                    return;
                }

                var tokenIn  = pair.GetToken(swap.Path.First());
                var tokenOut = pair.GetToken(swap.Path.Last());

                var swapMessage = $"๐Ÿ’ฑ Swap {Web3.Convert.FromWei(swap.AmountIn)} {tokenIn.Symbol} for {Web3.Convert.FromWei(swap.AmountOutMin, tokenOut.Decimals)} {tokenOut.Symbol} with gas price {Web3.Convert.FromWei(swap.GasPrice.GetValueOrDefault(), UnitConversion.EthUnit.Gwei)} Gwei";
                await SendPreFrontRunMessage(swapMessage, MessageTier.End | MessageTier.Single);

                try
                {
                    #region Validation

                    if (!_settingsOptions.YoloMode)
                    {
                        if (_pairWhitelist.TryGetValue(pair.Id, out var allowed) && !allowed)
                        {
                            await SendPreFrontRunMessage("โ›” This token is blacklisted from a previous validation", MessageTier.End);
                            return;
                        }

                        var swapChecks = new List <Func <Task <(bool, string)> > >
                        {
                            // Ignore transactions that are outside of the configured ETH range.
                            async() => (await Task.Run(() => swap.AmountIn.Between(Web3.Convert.ToWei(_settingsOptions.MinimumTargetSwapEth), Web3.Convert.ToWei(_settingsOptions.MaximumTargetSwapEth)), cancellationToken), "Amount to send outside of the configured range"),
                            // Ignore transactions with too high gas price.
                            async() => (await Task.Run(() => swap.GasPrice <= Web3.Convert.ToWei(_settingsOptions.MaximumGasPrice, UnitConversion.EthUnit.Gwei), cancellationToken), "Gas price higher than the configured limit")
                        };

                        var pairChecks = new List <Func <Task <(bool, string)> > >
                        {
                            // Check that the liquidity of the token pair is within the configured range.
                            async() => (await Task.Run(() => pair.ReserveUsd.Between((Rational)_settingsOptions.MinimumLiquidity, (Rational)_settingsOptions.MaximumLiquidity), cancellationToken), $"Liquidity (${pair.ReserveUsd.WholePart:N0}) outside of the configured range"),
                            // Check that the pair contract wasn't created too recently to protect against possible honeypots.
                            async() => (await Task.Run(() => pair.Created.AddDays(_settingsOptions.MinimumPairContractAge) < DateTime.UtcNow, cancellationToken), $"Pair contract created too recently ({pair.Created:g})"),
                            async() =>
                            {
                                // Check that there have been historical swaps selling the output token, to ensure the contract is legitimate.
                                var idName      = JsonHelper.GetJsonPropertyName <PairType>(nameof(PairType.Id));
                                var recentSwaps = new List <Models.Graph.Swap>();
                                int previousCount;

                                do
                                {
                                    // Retrieve the swaps in batches until there are no more fetched, or the configured threshold is reached.
                                    previousCount = recentSwaps.Count;
                                    recentSwaps.AddRange(_mapper.Map <IEnumerable <Models.Graph.Swap> >((await _graphService.SendQuery <SwapsResponse>(
                                                                                                             new Dictionary <string, string> {
                                        { idName, Graph.Types.Id }
                                    },
                                                                                                             new Dictionary <string, object>
                                    {
                                        { Graph.OrderBy, JsonHelper.GetJsonPropertyName <SwapType>(nameof(SwapType.Timestamp)) },
                                        { Graph.OrderDirection, Graph.Descending },
                                        { Graph.Where, new Dictionary <string, object>
                                          {
                                              { JsonHelper.GetJsonPropertyName <PairResponse>(nameof(PairResponse.Pair)), $"${idName}" },
                                              { $"{JsonHelper.GetJsonPropertyName<SwapType>(pair.GetToken(tokenIn.Id) == pair.Token0 ? nameof(SwapType.Amount0Out) : nameof(SwapType.Amount1Out))}{Graph.Gte}", _settingsOptions.HistoricalAnalysisMinEthOut },
                                              { $"{JsonHelper.GetJsonPropertyName<SwapType>(nameof(SwapType.Timestamp))}{Graph.Lt}", recentSwaps.Any() ? ((DateTimeOffset)recentSwaps.Last().Timestamp).ToUnixTimeSeconds() : DateTimeOffset.UtcNow.ToUnixTimeSeconds() }
                                          } }
                                    },
                                                                                                             new Dictionary <string, HashSet <string> >
                                    {
                                        { nameof(SwapType), new HashSet <string> {
                                              nameof(SwapType.Pair)
                                          } }
                                    },
                                                                                                             new { id = pair.Id },
                                                                                                             cancellationToken: cancellationToken)).Swaps));
                                } while (recentSwaps.Count.Between(previousCount, _settingsOptions.HistoricalAnalysisMinFetchThreshold, false));

                                var relevantSwaps = (await Task.WhenAll(recentSwaps.Select(async s => await _ethereumService.GetSwap(s.Transaction.Id)))).Where(s => s != null).ToArray();
                                if (relevantSwaps.Length < _settingsOptions.HistoricalAnalysisMinRelevantSwaps)
                                {
                                    return(false, $"Number of relevant swaps ({relevantSwaps.Length} out of {recentSwaps.Count}) is too low.");
                                }

                                var uniqueRecipients = relevantSwaps.GroupBy(s => s.To).Count();
                                return(uniqueRecipients / (double)relevantSwaps.Length < _settingsOptions.MinimumUniqueSellRecipientsRatio
                                    ? (false, $"Number of unique recipients in the relevant swaps ({uniqueRecipients} out of {relevantSwaps.Length}) is too low.")
                                    : (true, default));
Example #2
0
        public async Task Subscribe(CancellationToken cancellationToken)
        {
            // http://docs.nethereum.com/en/latest/nethereum-subscriptions-streaming/
            using var streamingClient = new StreamingWebSocketClient(_batBotOptions.BlockchainEndpointWssUrl);
            var subscription = new EthNewPendingTransactionObservableSubscription(streamingClient);

            await _ethereumService.HandleSubscription(streamingClient, subscription, async() => await subscription.SubscribeAsync(), async th =>
            {
                await _messagingService.SendLogMessage($"Pending Transaction Hash: {th}");

                // Don't process transactions (discard) when one is being processed for front running.
                if (_backoffService.BackOff())
                {
                    return;
                }

                await _backoffService.Protect(async() => await _transactionProcessorService.Process(await _ethereumService.GetSwap(th), cancellationToken));
            }, null, cancellationToken);
        }